Hello,
I would like to add a java 11 variant to a java 8 library and I probably not use the correct Gradle API to do that but I cannot pinpoint where is my error.
The goals are:
- to be able to publish a java library that can be executed with Java 8;
- hard constraint: that library consumes APIs that were removed in the Java 11 API (such as JAXB in
javax.xml
package for instance), - soft constraint: the build should work using a Java 8 or a Java 11 toolchain a-like.
- hard constraint: that library consumes APIs that were removed in the Java 11 API (such as JAXB in
- the consumers of this library may be Gradle or Maven builds;
- hard constraint: it is expected that their build tools are up-to-date.
- the consumers may build their projects using a Java 8 or a Java 11 toolchain;
- hard constraint: a consumer using a Java 8 toolchain must not use dependencies required for Java 11 in its compile/runtime classpath.
I start with modeling a multi projects build with a producer
and a consumer
:
settings.gradle
rootProject.name = 'test'
include 'producer', 'consumer'
producer/build.gradle
plugins {
id 'java-library'
}
java {
registerFeature('java11') {
usingSourceSet(sourceSets.main)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
configurations {
java11ApiElements {
attributes {
attribute TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11
}
}
java11RuntimeElements {
attributes {
attribute TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11
}
}
}
The outgoing variants seem to be well configured:
~/test2$ ./gradlew :producer:outgoingVariants
> Task :producer:outgoingVariants
--------------------------------------------------
Variant apiElements
--------------------------------------------------
Description = API elements for main.
Capabilities
- test:producer:unspecified (default capability)
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 8
- org.gradle.libraryelements = jar
- org.gradle.usage = java-api
Artifacts
- build/libs/producer.jar (artifactType = jar)
Secondary variants (*)
- Variant : classes
- Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 8
- org.gradle.libraryelements = classes
- org.gradle.usage = java-api
- Artifacts
- build/classes/java/main (artifactType = java-classes-directory)
--------------------------------------------------
Variant java11ApiElements
--------------------------------------------------
Description = API elements for feature java11
Capabilities
- test:producer-java11:unspecified
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 11
- org.gradle.libraryelements = jar
- org.gradle.usage = java-api
Artifacts
- build/libs/producer.jar (artifactType = jar)
Secondary variants (*)
- Variant : classes
- Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 11
- org.gradle.libraryelements = classes
- org.gradle.usage = java-api
- Artifacts
- build/classes/java/main (artifactType = java-classes-directory)
--------------------------------------------------
Variant java11RuntimeElements
--------------------------------------------------
Description = Runtime elements for feature java11
Capabilities
- test:producer-java11:unspecified
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 11
- org.gradle.libraryelements = jar
- org.gradle.usage = java-runtime
Artifacts
- build/libs/producer.jar (artifactType = jar)
--------------------------------------------------
Variant runtimeElements
--------------------------------------------------
Description = Elements of runtime for main.
Capabilities
- test:producer:unspecified (default capability)
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 8
- org.gradle.libraryelements = jar
- org.gradle.usage = java-runtime
Artifacts
- build/libs/producer.jar (artifactType = jar)
Secondary variants (*)
- Variant : classes
- Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 8
- org.gradle.libraryelements = classes
- org.gradle.usage = java-runtime
- Artifacts
- build/classes/java/main (artifactType = java-classes-directory)
- Variant : resources
- Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 8
- org.gradle.libraryelements = resources
- org.gradle.usage = java-runtime
- Artifacts
- build/resources/main (artifactType = java-resources-directory)
(*) Secondary variants are variants created via the Configuration#getOutgoing(): ConfigurationPublications API which also participate in selection, in addition to the configuration itself.
BUILD SUCCESSFUL in 869ms
1 actionable task: 1 executed
The consumer
now:
consumer/build.gradle
plugins {
id 'java-library'
}
repositories {
mavenCentral()
}
dependencies {
implementation project(':producer')
}
According to the documentation Working with Variant Attributes, I expect that the consumer uses the java 11 variant because the compatibility and disambiguation rules state that:
Attribute named
org.gradle.jvm.version
“Defaults to the JVM version used by Gradle, lower is compatible with higher, prefers highest compatible.”
And the current VM is Java 11:
~/test2$ ./gradlew --version
------------------------------------------------------------
Gradle 6.8.2
------------------------------------------------------------
Build time: 2021-02-05 12:53:00 UTC
Revision: b9bd4a5c6026ac52f690eaf2829ee26563cad426
Kotlin: 1.4.20
Groovy: 2.5.12
Ant: Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM: 11.0.10 (Oracle Corporation 11.0.10+9)
OS: Linux 5.10.19-1-lts amd64
Yet, it picks the normal variant:
~/test2$ ./gradlew :consumer:dependencyInsight --dependency=producer
> Task :consumer:dependencyInsight
project :producer
variant "apiElements" [
org.gradle.category = library
org.gradle.dependency.bundling = external
org.gradle.usage = java-api
org.gradle.libraryelements = jar (compatible with: classes)
org.gradle.jvm.version = 8 (compatible with: 11)
]
project :producer
\--- compileClasspath
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 843ms
1 actionable task: 1 executed
If I change the consumer.gradle to:
dependencies {
implementation(project(':producer')) {
capabilities {
requireCapability("${project.group}:producer-java11:${project.version}")
}
}
}
Then, Gradle picks the correct variant:
~/test2$ ./gradlew :consumer:dependencyInsight --dependency=producer
> Task :consumer:dependencyInsight
project :producer
variant "java11ApiElements" [
org.gradle.category = library
org.gradle.dependency.bundling = external
org.gradle.usage = java-api
org.gradle.libraryelements = jar (compatible with: classes)
org.gradle.jvm.version = 11
]
project :producer
\--- compileClasspath
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
I’d like to avoid adding capabilities to my dependencies as this would mean that every consumer of my lib should do the same. I would like to leverage the target vm attribute set by Gradle if possible.
The next step would be to add the missing API removed from Java 11 to the java11Implementation
configurations and consume them only if the java 11 variant is picked.
I surely must have overlooked something important regarding variant definition and/or consumption but I cannot pinpoint it. A bit of help would be welcome.