Feature Variants: No matching variant found error

I have a plugin which uses kotlin 1.9.x by default and hence supports only gradle 8.x or above. I want to add optional support for gradle 7.x via feature variants via lowering the kotlin version for the gradle7 variant, but when the consumer (with gradle 7.x) is trying to import the plugin, it gets a “No matching variant found”

I’m not entirely sure what I’m missing to get this working, but here’s a diff of what I’ve added to enable supporting gradle7 - Comparing master...gradle7 · serpro69/semver.kt · GitHub

The error I’m getting is this:

Could not resolve all artifacts for configuration 'classpath'.
> Could not resolve io.github.serpro69:semantic-versioning:0.0.0-dev.
  Required by:
      unspecified:unspecified:unspecified > io.github.serpro69.semantic-versioning:io.github.serpro69.semantic-versioning.gradle.plugin:0.0.0-dev
   > No matching variant of io.github.serpro69:semantic-versioning:0.0.0-dev was found. The consumer was configured to find a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2' but:
       - Variant 'apiElements' capability io.github.serpro69:semantic-versioning:0.0.0-dev declares a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
           - Incompatible because this component declares an API of a component, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' and the consumer needed a runtime of a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
       - Variant 'gradle7ApiElements' capability io.github.serpro69:semantic-versioning-gradle7:0.0.0-dev declares a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2':
           - Incompatible because this component declares an API of a component and the consumer needed a runtime of a component
       - Variant 'gradle7RuntimeElements' capability io.github.serpro69:semantic-versioning-gradle7:0.0.0-dev declares a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
       - Variant 'javadocElements' capability io.github.serpro69:semantic-versioning:0.0.0-dev declares a runtime of a component, and its dependencies declared externally:
           - Incompatible because this component declares documentation, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' and the consumer needed a library, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
           - Other compatible attributes:
               - Doesn't say anything about its target Java version (required compatibility with Java 17)
               - Doesn't say anything about its elements (required them packaged as a jar)
       - Variant 'runtimeElements' capability io.github.serpro69:semantic-versioning:0.0.0-dev declares a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
           - Incompatible because this component declares a component, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' and the consumer needed a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
       - Variant 'sourcesElements' capability io.github.serpro69:semantic-versioning:0.0.0-dev declares a runtime of a component, and its dependencies declared externally:
           - Incompatible because this component declares documentation, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' and the consumer needed a library, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
           - Other compatible attributes:
               - Doesn't say anything about its target Java version (required compatibility with Java 17)
               - Doesn't say anything about its elements (required them packaged as a jar)

Particularly, I think I need to fix this one:

- Variant 'gradle7ApiElements' capability io.github.serpro69:semantic-versioning-gradle7:0.0.0-dev declares a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2':
           - Incompatible because this component declares an API of a component and the consumer needed a runtime of a component

since the gradle7RuntimeElements seems to be ok, and I don’t think I need to bother about the rest?

I’m not sure why this reason is shown, because I’ve pretty much declared same things for both default variant and the gradle7 one, with only difference being kotlin dependencies version. And the default variant works just fine with gradle 8.+

Documentation on feature variants is quite scarce so I’m hoping someone who knows well enough how it works could help me fix this.

Thanks in advance.

You simply missed to set the capability, as I showed in that other thread.
Your gradle7... variants have capability io.github.serpro69:semantic-versioning-gradle7:0.0.0-dev, but to get them automatically selected by attributes, they have to have the requested capability which is io.github.serpro69:semantic-versioning:0.0.0-dev.
Replace

registerFeature("gradle7") {
    usingSourceSet(sourceSets.main.get())
}

by

registerFeature("gradle7") {
    usingSourceSet(sourceSets.main.get())
    capability("$group", project.name, "$version")
}

like I showed in that other thread and it should probably work.

Feature variants with differing capabilities are for feature variants that are selected explicitly using requireCapability like “give me oracle db support variant” or “give me mssql db support variant” …

Thanks for reply @Vampire ! Really appreciate your inputs on these forums.
Your suggestion din’t help unfortunately. From what I can tell, all that does is allowing me to not specify capability in an internal dependency explicitly like I was doing earlier (e.g. semver.kt/release/build.gradle.kts at 31ce70a1439ad079ba148ac7c687803ff13bfd8e · serpro69/semver.kt · GitHub ). But I’m still getting the same error on the consumer side, though now it strangely complains about another submodule:

FAILURE: Build failed with an exception.

* What went wrong:
Could not resolve all artifacts for configuration 'classpath'.
> Could not resolve io.github.serpro69:semver.kt-release:0.0.0-dev.
  Required by:
      unspecified:unspecified:unspecified > io.github.serpro69.semantic-versioning:io.github.serpro69.semantic-versioning.gradle.plugin:0.0.0-dev > io.github.serpro69:semantic-versioning:0.0.0-dev
   > No matching variant of io.github.serpro69:semver.kt-release:0.0.0-dev was found. The consumer was configured to find a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2' but:
       - Variant 'apiElements' capability io.github.serpro69:semver.kt-release:0.0.0-dev declares a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
           - Incompatible because this component declares an API of a component, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' and the consumer needed a runtime of a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
       - Variant 'gradle7ApiElements' capability io.github.serpro69:release:0.0.0-dev declares a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2':
           - Incompatible because this component declares an API of a component and the consumer needed a runtime of a component
       - Variant 'gradle7RuntimeElements' capability io.github.serpro69:release:0.0.0-dev declares a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'
       - Variant 'runtimeElements' capability io.github.serpro69:semver.kt-release:0.0.0-dev declares a runtime of a library compatible with Java 17, packaged as a jar, and its dependencies declared externally:
           - Incompatible because this component declares a component, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' and the consumer needed a component, as well as attribute 'org.gradle.plugin.api-version' with value '7.2'

It is because of your bad-practice artifact ID manipulation which also influences the default capability.
If you look at your latest output closely, you see that
runtimeElements has capability io.github.serpro69:semver.kt-release:0.0.0-dev while
gradle7RuntimeElementshas capability io.github.serpro69:release:0.0.0-dev.

So either set the capability of the main variant explicitly too, or set the capability of the gradle7 variant accordingly so that in the end they are equal.

As I said before the selection only by attributes only works if both have the same capability.

1 Like

Ah… :man_facepalming: I see what you mean…

The moment I did that artifactId manipulation I knew it was going to come back and bite me at one point or another :joy:

I’ve fixed that now, and the “no variant” error is gone, but the issue that I’ve tried to fix originally is back: the kotlin version detection.

* What went wrong:
kotlin/enums/EnumEntriesKt
> kotlin.enums.EnumEntriesKt

Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.NoClassDefFoundError: kotlin/enums/EnumEntriesKt [in thread "Daemon worker Thread 22"]

This EnumEntriesKt class was added in kotlin 1.9, and that’s what I’ve been trying to solve with downgrading the kotlin version for gradle7 variant. However, it appears that the consumer still picks up kotlin 1.9 and I think I finally figured out why.

Looking at Implementing Gradle plugins , the #4 says:

Note that there is currently no convenient way to access the API of other Gradle versions as the one you are building the plugin with. Ideally, every variant should be able to declare a dependency to the API of the minimal Gradle version it supports. This will be improved in the future.

Which means, if the plugin is built with gradle8, it will bring with it api of v8, which in turn brings with it kotlin 1.9

I’m not sure if there’s any workaround for this - I haven’t yet found a way to declare gradle api with a specific version, so looks like what I’m trying to do is currently not possible.
But at least I’ve learned some stuff in the process.

Thanks again for your help @Vampire and happy holidays :slightly_smiling_face:

UPD: I just tried to build with a lower gradle version and my above assumption was incorrect. The culprit is the kotlin plugin itself. It seems like it brings with it some transitive kotlin dependencies, and apparently w/o downgrading the plugin (I’ve also tested out this option now and it finally worked as expected) you can’t really lower kotlin dependnecies even when you set strict versions. Very weird, but well, what can you do… I’m disappointed in kotlin more and more these days :laughing:

I don’t think what you understood is right.
The Gradle API itself brings not any Kotlin dependency at all as it is not written in Kotlin.

But the runtime environment and which Kotlin version is available there is controlled by the Gradle version where your plugin is applied.

I don’t know for sure without deeper investigating, but I guess the problem is, that you compile against 1.9 and thus have the 1.9 class in your bytecode. Then at runtime you only have, the Kotlin version Gradle provides and thus miss that class or something like that.

So you probably need to compile against the lowest Kotlin version supported in the lowest Gradle version you want to support. Compiling against 1.9 and just providing a variant with other Kotlin dependency is pretty pointless. Actually, you should most probably anyway just have it as compileOnly dependency, as Gradle controls the Kotlin version in the runtime environment.

Such problems are one of the causes why I would never use Kotlin for a public plugin that should be as compatible as possible, but only pure Java and there the lowest version supported by the lowest Gradle version I want to support. So currently I would implement Gradle plugins only in Java 8 for maximum compatibility and minimal chance for f*** up. :slight_smile:

1 Like

And btw. “there is currently no convenient way to access the API of other Gradle versions as the one you are building the plugin with” is also not correct.

No built-in way, yes.

But that is also changing with Improved support for plugins targeting multiple Gradle versions · Issue #70 · gradle/build-tool-roadmap · GitHub

And in the meantime there is Gradle plugin development

1 Like

Thanks for your inputs @Vampire !
I’ll look more into this after holidays. I’m quite new to developing gradle plugins, and the info you’ve provided me here is very useful. Thanks!

Such problems are one of the causes why I would never use Kotlin for a public plugin that should be as compatible as possible, but only pure Java and there the lowest version supported by the lowest Gradle version I want to support. So currently I would implement Gradle plugins only in Java 8 for maximum compatibility and minimal chance for f*** up.

I’ve been thinking about the same lately - if I want max compatibility then I need to ditch kotlin.

1 Like