Skip to content

Conversation

@martinbonnin
Copy link
Member

No description provided.

@martinbonnin martinbonnin merged commit 314aeab into main Aug 8, 2025
1 check passed
@martinbonnin martinbonnin deleted the check-publishingType branch August 8, 2025 20:01

override fun centralPortal(action: Action<CentralPortalOptions>) {
check(!centralPortalConfigured) {
"Nmcp: centralPortal {} must be called only once"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that requirement?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

centralPortal {} creates the tasks so calling it twice fails with "task already exists" the second time.

I know Gradle wants you to be declarative and register the task eagerly in Plugin.apply() but I found out that:

  1. this pollutes the model if no publishing is done
  2. it makes it unclear the precedence of setting the property in case multiple users set the properties.

I have wasted days/weeks of my life trying to understand what changed the value of a given property under the hood. This solution, while less flexible makes it clear that .gradle.kts files are imperative, not declarative and who owns a given property. This should obviously be revisited for declarative Gradle.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your pain, however, the approach of "no one can override" effectively blocks overriding the value should one need it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, it's always a trade-off.

To override a value, you'll have to wrap whatever convention plugin is setting it in the first place so there is ultimately a single source of truth for the value.

If you don't own the convention plugin then you'll have to talk with the plugin maintainers so that they open an API for you.

It's painful but so is the debugging of Gradle lifecyle. Which one is worst is a legit question that hopefully declarative Gradle will answer.

Comment on lines +139 to +141
val validValues = listOf("AUTOMATIC", "USER_MANAGED")
check(publishingType == null || publishingType in validValues) {
"Nmcp: invalid Central Portal publishingType value '$publishingType'. Must be one of ${validValues}."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only there was a way like data objects to prevent invalid values at the configuration time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is your point that publishingType should be an enum? (data objects probably won't do it for Groovy callers?).

I think we discussed that in the past. I'm with you on the theory. Type-safety is better.

But in that specific case, I found out that in practice, the IJ imports are not working so well in .gradle.kts files, importing symbols doesn't work in applied .gradle files because the symbols are not in the compile classpath of those scripts. Also a plain String allows adding new values without having to update the plugin, may sonatype decide to add more values. As with everything, it's a tradeoff.

Might change it for v2 once we have more feedbacks about the API and the tooling has improved.

Copy link
Contributor

@vlsi vlsi Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK Kotlin's data objects (== static final ... fields) should be perfectly usable with both Java and Groovy.

For instance:

sealed class PublishingType {
    data object Automatic : PublishingType()
    data object UserManaged : PublishingType()
    data class Other(val value: String) : PublishingType()
}

Both Groovy and Java should support cases like publishingType = PublishingType.Automatic.INSTANCE or publishingType = new PublishingType.Other("some_new_value_added_by_sonatype")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes of course. No exhaustive when there but it's not an issue at all in that case and allows the Other case 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the key point is to have an escape hatch for the case if Sonatype adds a new publication type that is not yet supported in the plugin. At the same time, well-known cases can have type-safe APIs.

Copy link
Contributor

@vlsi vlsi Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth adding static final accessors to reduce .INSTANCE boilerplate, however, I'm not sure how common is it:

sealed class PublishingType {
    data object Automatic : PublishingType()
    data object UserManaged : PublishingType()
    data class Other(val value: String) : PublishingType()

    companion object {
        @JvmField val AUTOMATIC: PublishingType = Automatic
        @JvmField val USER_MANAGED: PublishingType = UserManaged
    }
}

Then Java/Groovy should be able to access values like in publishingType = PublishingType.AUTOMATIC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants