does gradle retain metadata about what version of ...
# community-support
c
does gradle retain metadata about what version of gradle a plugin works with? can it?
g
There are variant attributes that a plugin can define to declare compatibility (and publish variants targeting specific versions of Gradle): Gradle plugin ecosystem specific attributes
👍 2
t
Anyone knows plugins actually using it to signal compatibility? (and not because they have gradle-specific variants? I think maybe Kotlin and Spring Boot use it for variants; Spring Boot removed it in 3.4 prereleases when they switched back to a single variant)
g
Code search shows a few (can't tell from these snippets if it's ending up in the actual artifacts though)
t
Most seem to use it for resolution though, not publication.
👍🏻 1
v
I've seen some, but have no at hand right now
m
What's the advantage of this compared to adding a check in
Plugin::apply
?
Copy code
require(gradleVersion >= minVersion)
You get the error a bit earlier I guess?
v
Not just that, you can have different variants for different Gradle versions, that's the main use-case
c
additionally reduced complexity in your code
v
You can not specify a max version though with the attributes, just a min version
For a max version you would then need an additional variant that is used instead and fails or something like that
t
additionally reduced complexity in your code
You're trading an imperative if/throw (2 LoC) in the plugin with an additional publication attribute (slightly more than 2 LoC in your build file).
☝️ 1
m
There's not that much code in the KGP variants though . If it were me, I would probably compile against max version and guard newer APIs with a bunch of
if(gradleVersion > versionWhereTHeAPIWasIntroduced) {}
.
c
> You're trading an imperative if/throw (2 LoC) in the plugin with an additional publication attribute (slightly more than 2 LoC in your build file). yes but
Plugin::apply
is "business logic", in theory gradle could stop an upgrade/downgrade from ever happening. Checking versions shouldn't be part of my business logic unless my plugin deals specifically with versions. Also for certain complexity metrics in increases the likelyhood of failure, and I do use checkstyles complexity metric checks. Essentially that check doesn't really have anything to do with my plugins functionality. In the same way that java's bytecode doesn't, but it might affect a consumer
t
KGP publishes several variants
Yes, Kotlin and Spring Boot (since 3.2.6 up to 3.3.x: https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-gradle-plugin/3.3.5/spring-boot-gradle-plugin-3.3.5.module) publish variants. Does any plugin uses the attribute only to replace the version check in the code though?
Note that the attribute has been added to Gradle 7.0, so any previous version won't handle it (can't remember if it would cause it to fail resolution –in case there's only one variant– or not). If your plugin is compatible with some Gradle 6.x versions, the attribute won't work.
But otherwise, well, as I said, it's roughly the same amount of code/complexity, so I'd say test the behavior in incompatible Gradle versions and if it feels OK then pick the flavor you prefer.
c
sure, I'm just justifying why I'd put it out... although part of this problem is "I don't know" a future version could break something, or a past version could be broken and I've never tested
Personally I'd rather just repub on gradle 9, although the past is easier to know 😉
t
Oh, BTW, using the attribute also means you expect the plugin to be resolved in a way that uses that attribute.
buildSrc/
build apparently adds the attribute to its configurations, but not in subprojects, and included builds that people use as an alternative to
buildSrc
won't add it automatically, so I'd say many people don't actually resolve plugins using that attribute (unless they depend on a plugin that specifically publishes several variants –see all those matches in the code search link shared above, to resolve the Kotlin or Spring Boot plugins–, as they need a mean of selecting the appropriate variant).
m
That's a good point
I'm surprised me importing KGP works at all 😄
What flavor is used if the consumer doesn't set an attribute? I'm guessing the minimum?
I might be missing on some build improvements 🤔
e
KGP publishes flavors with attributes and one "default" without
👍 1
as long as they keep the public API/ABI the same across the variants, it's fine for you to have a plugin that builds against the "base" variant. once it's actually used by a build, it'll resolve to the appropriate one then
m
I'm using
build-logic
so I think it'll also use the "default" one for resolution
v
If your plugin is compatible with some Gradle 6.x versions, the attribute won't work.
That's not correct. You can for example have one variant that is used for >7 and from 7 on you can have variants where your like, one for 7+, or one for 7-8 and one for 9+, or one for 7 one for 8 one for 9+, ... If your support many different Gradle versions and the plugin is a bit more complex, you can end up with many version checks and code separated in different classes for different versions, or reflection used for some parts and so on. And there using variants can indeed greatly reduce complexity, increase maintainability, increase testability, and reduce error proneness. As a simple replacement for a minimum version check it can be used, but is not that much different. Mainly matter of taste.
but not in subprojects, and included builds that people use as an alternative to buildSrc won't add it automatically, so I'd say many people don't actually resolve plugins using that attribute
Iirc this is not true. It probably is the plugin development plugin that adds the attribute and that you typically will have applied when your develop a plugin.
But optimally ABI is the same anyway and at runtime the appropriate variant is chosen, so shouldn't matter much, yeah.
m
Copy code
println("KGP is at " + KotlinMultiplatformPluginWrapper::class.java.protectionDomain.codeSource.location.toString())
KGP is at file:/Users/martinbonnin/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-gradle-plugin/2.0.20/5ca90c0173fffe0f4e9b73aca425fd99213cc43a/kotlin-gradle-plugin-2.0.20-gradle85.jar
So it does seem to work 👍
Just when I thought I was understanding how buildscript classpath resolution was working 🙃
Also works if I dump the
build-logic:compileClasspath
Which is a bit more surprising but at this point I'm happy to just accept the results
v
Yep, but to also answer the other question, if only variants with the attribute are published and multiple, resolution should fail with ambiguity if the attribute is not requested as in Gradle 6. If one variant without the attribute is there and one or more with the attribute and the attribute is not requested, the one without is used according to last step in the resolution process description. Less not-requested extra attributes wins.
👍 1
e
buildscript classpath resolution isn't any different from resolution elsewhere
if your library A depend on library B, and a consumer of library A requests certain attributes that a variants of library B will satisfy, then that's what the consumer will get
m
But someone has to set the attribute. How does
build-logic
knows that my
compileClasspath
is the one of a Gradle plugin?
e
why does it need to? the consuming buildscript
plugins { id("build-logic") }
does
m
Mmmm for runtime classpath I would get that. I find the fact that the attribute is also set for the compile classpath a bit suprising.
build-logic
could very well be a completely standalone build
Or does Gradle always set the
org.gradle.plugin.api-version
attribute?
For each and every configuration?
e
I'm not sure why you think that it does?
m
2min, trying out something...
I just did a very simple build script, no plugins, no nothing, just that code:
Copy code
// build.gradle.kts
val configuration = configurations.create("test")
configuration.dependencies.add(dependencies.create("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21"))

println(configuration.files.filter { it.name.contains("kotlin-gradle-plugin-2.0.21") })
It prints this out:
Copy code
.../kotlin-gradle-plugin-2.0.21-gradle85.jar
So when resolving the
"test"
configuration, looks like
org.gradle.plugin.api-version
is set (because it knows I'm running Gradle >= 8.5)
Trying to run with Gradle 8.4 now
Yup, running the same thing with Gradle 8.4 prints this:
Copy code
.../kotlin-gradle-plugin-2.0.21-gradle82.jar
which is somewhat consistent.
Just find it a bit surprising that the default is to set
org.gradle.plugin.api-version
to the current Gradle version because a configuration in general can be used in very different places. But ABI is the same and it's all working so all good, just a bit suprising IMO
v
I checked the sources. Might miss something as I'm on mobile. But the attribute should only be set if you are in
buildSrc
or applied the
java-gradle-plugin
.
But actually you can also check requested and provided attributes using the
dependencyInsight
task
💡 1
m
Copy code
$ ./gradlew dependencyInsight --configuration test --dependency "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21"

> Task :dependencyInsight
org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21 (by constraint)
  Variant gradle82RuntimeElements:
    | Attribute Name                 | Provided     | Requested |
    |--------------------------------|--------------|-----------|
    | org.gradle.category            | library      |           |
    | org.gradle.dependency.bundling | external     |           |
    | org.gradle.jvm.environment     | standard-jvm |           |
    | org.gradle.jvm.version         | 8            |           |
    | org.gradle.libraryelements     | jar          |           |
    | org.gradle.plugin.api-version  | 8.2          |           |
    | org.gradle.status              | release      |           |
    | org.gradle.usage               | java-runtime |           |
(I'm still running Gradle 8.4)
So it doesn't look like the attribute is requested. But it still resolves to the 8.2 KGP
Trying to change to Gradle 8.10 again
Copy code
| org.gradle.plugin.api-version  | 8.5          |           |
So something in the resolution KNOWS 🙂
v
o_O
Strange
t
> If your plugin is compatible with some Gradle 6.x versions, the attribute won't work.
That's not correct.
I was talking about using the attribute as a "version check" in a case where you have a single variant; in this case the attribute "won't work" as setting its value to e.g. "6.8" won't prevent Gradle 6.5 to resolve the plugin. If your plugin supports Gradle < 7, then you have no choice but to use an if/throw in your plugin's code.
👌 1
m
Alright, I think I got down to this rabbit hole. Magic is happening here There's a disambiguation rule that is installed for each configuration (not 100% sure about that part but it sure does look like it) and that will select the highest possible variant
I'm now thinking of creative ways to prank some users with random
org.gradle.plugin.api-version
in my libraries 😈
t
There's a disambiguation rule that is installed for each configuration (not 100% sure about that part but it sure does look like it)
A disambiguation rule is linked to the attribute (as declared in the attribute schema), so it's used for each and every resolution (i.e. each configuration)
m
That's bonkers
Better be explicit about the attribute then if you have such dependencies in your graph
I'd say throwing would probably have been a better default
t
🤔 How would you expect Gradle to pick the appropriate variant for your version of Gradle then? (the only case where the disambiguation rule won't be used is if you request with the exact same value as a variant; the rule will otherwise select the variant with the highest value that's lower than the requested value; so Gradle will pick the 7.0 variant of KGP if you request the 7.0, or 7.0.2, the 7.1 variant if you request the 7.1 or 7.1.1, or 7.2, or 7.3, the 7.4 variant if you request 7.4, etc. and finally the 8.5 variant if you request any value >= 8.5)
e
I was expecting it to be applied in buildscripts and plugin builds only
but that does appear to be global and default
v
Alright, I think I got down to this rabbit hole. Magic is happening here
Ah, right, of course. 🙈 Compatibility rules only kick in if both sides (consumer and producer) have a value set. But disambiguation rules are also used if the attribute is not requested but multiple variants provide different values.
There's a disambiguation rule that is installed for each configuration (not 100% sure about that part but it sure does look like it) and that will select the highest possible variant
Not on each configuration as they are not bound to configurations. They are added to the attribute matching strategy of the attribute schema which is directly on the whole
DependencyHandler
.
the only case where the disambiguation rule won't be used is if you request with the exact same value as a variant; the rule will otherwise select the variant with the highest value that's lower than the requested value
Not exactly, if the documented algorithm is really like written there. It says
1. Each candidate’s attribute value is compared to the consumer’s requested attribute value. A candidate is considered compatible if its value matches the consumer’s value exactly, passes the attribute’s compatibility rule or is not provided.
So even when requesting the exact attribute you should still get that and all higher ones as candidates and the disambiguation rule will then pick the closest match. Unless I misread the algorithm description or it is incorrect of course. 😄 So I think it would even be used if there is an exact match as long as there is also an inexact match.
I was expecting it to be applied in buildscripts and plugin builds only
That the attribute is requested is only in build scripts and plugin builds. But the disambiguation rule is always configured and used, yes. Like JVM attributes are always added to the schema with their compatibility and disambiguation rules if the
jvm-ecosystem
plugin is applied, Gradle-related attributes are always added to the schema with their compatibility and disambiguation rules if Gradle is involved, so always. 😄
m
> I was expecting it to be applied in buildscripts and plugin builds only Same. Request the attribute explicitly in places where you know you're used from a Gradle build. If you don't know, fail and let the user specify the Gradle version they target. > if Gradle is involved, so always I get it but still feels like a dangerous default. The Gradle version that I use to build is not the Gradle version that my software targets.
v
I don't disagree that this is questionable. But how is "The Gradle version that I use to build is not the Gradle version that my software targets." relevant? If it is then used in another Gradle version, then there the resolution will pick the according variant, no matter what you built against. That's why it probably is a good idea to have the same ABI in all variants I guess.
g
I also see the point of removing the default. However, one can argue that either the plugin author is actively thinking about supporting/targeting a different version than the version used to build, in which case he's probably testing against the target version(s) and setting the attribute, or he's not thinking about it and is only testing against and publishing for the version used to build, in which case it seems like a reasonable default (i.e. min is the version you're most likely to have tested in).
m
Yea, agreed. This is mainly a theoretical issue. In practice, there has been no issue. Maybe also due to the fact that the usage seems confidential, who knows. Let’s revisit in 2030 when everyone ships plugin variants 😄
c
not certain about variants, but I wonder how many plugins on the plugin portal still work with a modern version if they haven't been updated in years
m
I mean if no one has incentive to keep them up to date, they’re probably not that needed
But true that the Gradle compat story is not as good as say the Java one
c
probably, but there's also not a good way to tell if it is that way or just is perceived as "working"
m
Yup
c
me wishing for, what was it? cpantesters... where people were basically chaos monkeying people's libraries and publishing pass/fail results as a form of quality metric. Occasionally they'd do something I thought was annoying, but overall it would solve such problems
👀 1
but no one does that in javaland