intellij-platform-gradle-plugin
intellij-platform-gradle-plugin copied to clipboard
Gradle plugin does not respect all possible ways to configure java source target compatibility and produces variants with a wrong jvm version
What happened?
Currently no other ways of configuring source compatibility is supported except:
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
while Gradle recommends to use instead e.g.
> When Java code is compiled using a specific toolchain, the actual compilation is carried out by a compiler of the specified Java version. The compiler provides access to the language features and JDK APIs for the requested Java language version.
> In the simplest case, the toolchain can be configured for a project using the java extension. This way, not only compilation benefits from it, but also other tasks such as test and javadoc will also consistently use the same toolchain.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
or
> Setting the release flag ensures the specified language level is used regardless of which compiler actually performs the compilation. To use this feature, the compiler must support the requested release version. It is possible to specify an earlier release version while compiling with a more recent toolchain.
tasks.compileJava {
options.release = 7
}
or:
withType<JavaCompile> {
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
}
which is not good because the "java {" way does not do this:
The release flag provides guarantees similar to toolchains. It validates that the Java sources are not using language features introduced in later Java versions, and also that the code does not access APIs from more recent JDKs. The bytecode produced by the compiler also corresponds to the requested Java version, meaning that the compiled code cannot be executed on older JVMs.
Relevant documentation https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_cross_compilation
This may be useful: https://github.com/gradle/gradle/blob/master/platforms/jvm/plugins-java-base/src/main/java/org/gradle/api/plugins/jvm/internal/DefaultJvmLanguageUtilities.java#L82
This came out of https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1713 See https://github.com/AlexanderBartash/intellij-platform-plugin-template/blob/issue-1713/common/build.gradle.kts#L50
Try running "./gradlew :common:outgoingVariants" with and without this "java {" block
//java {
// sourceCompatibility = JavaVersion.VERSION_1_7
// targetCompatibility = JavaVersion.VERSION_1_7
//}
The difference:
--------------------------------------------------
Variant intellijPlatformComposedJar
--------------------------------------------------
Capabilities
- org.jetbrains.plugins.template:common:unspecified (default capability)
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.environment = standard-jvm
- org.gradle.jvm.version = 7 <<<<<<<<<<<<<<<<<<< with this configuration
- org.gradle.libraryelements = composed-jar
- org.gradle.usage = java-runtime
- org.jetbrains.kotlin.platform.type = jvm
Artifacts
- build/libs/common.jar (artifactType = jar)
--------------------------------------------------
Variant intellijPlatformComposedJar
--------------------------------------------------
Capabilities
- org.jetbrains.plugins.template:common:unspecified (default capability)
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.environment = standard-jvm
- org.gradle.jvm.version = 17 <<<<<<<<<<<<<<<<<<< without this configuration
- org.gradle.libraryelements = composed-jar
- org.gradle.usage = java-runtime
- org.jetbrains.kotlin.platform.type = jvm
Artifacts
- build/libs/common.jar (artifactType = jar)
Relevant log output or stack trace
No response
Steps to reproduce
See https://github.com/AlexanderBartash/intellij-platform-plugin-template/blob/issue-1713/common/build.gradle.kts#L50
Gradle IntelliJ Plugin version
2.0.1
Gradle version
8.10.2
Operating System
Linux
Link to build, i.e. failing GitHub Action job
No response
I did a couple of tests to see how different configurations behave.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.JETBRAINS
}
}
//tasks.compileJava {
// options.release = 7
//}
//tasks {
// withType<JavaCompile> {
// sourceCompatibility = "1.7"
// targetCompatibility = "1.7"
// }
//}
//java {
// sourceCompatibility = JavaVersion.VERSION_1_7
// targetCompatibility = JavaVersion.VERSION_1_7
//}
println("java.sourceCompatibility = " + java.sourceCompatibility)
println("java.targetCompatibility = " + java.targetCompatibility)
tasks.withType<JavaCompile> {
println("withType<JavaCompile> sourceCompatibility = $sourceCompatibility")
println("withType<JavaCompile> targetCompatibility = $targetCompatibility")
}
tasks.compileJava {
println("tasks.compileJava options.release = " + options.release.get())
}
Output:
java.sourceCompatibility = 11
java.targetCompatibility = 11
withType<JavaCompile> sourceCompatibility = 11
withType<JavaCompile> targetCompatibility = 11
withType<JavaCompile> sourceCompatibility = 11
withType<JavaCompile> targetCompatibility = 11
Cannot query the value of task ':common:compileJava' property 'options.release' because it has no value available.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.JETBRAINS
}
}
tasks.compileJava {
options.release = 7
}
//tasks {
// withType<JavaCompile> {
// sourceCompatibility = "1.7"
// targetCompatibility = "1.7"
// }
//}
//java {
// sourceCompatibility = JavaVersion.VERSION_1_7
// targetCompatibility = JavaVersion.VERSION_1_7
//}
println("java.sourceCompatibility = " + java.sourceCompatibility)
println("java.targetCompatibility = " + java.targetCompatibility)
tasks.withType<JavaCompile> {
println("withType<JavaCompile> sourceCompatibility = $sourceCompatibility")
println("withType<JavaCompile> targetCompatibility = $targetCompatibility")
}
tasks.compileJava {
println("tasks.compileJava options.release = " + options.release.get())
}
Output:
java.sourceCompatibility = 11
java.targetCompatibility = 11
withType<JavaCompile> sourceCompatibility = 1.7
withType<JavaCompile> targetCompatibility = 1.7
withType<JavaCompile> sourceCompatibility = 11
withType<JavaCompile> targetCompatibility = 11
tasks.compileJava options.release = 7
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.JETBRAINS
}
}
//tasks.compileJava {
// options.release = 7
//}
tasks {
withType<JavaCompile> {
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
}
}
//java {
// sourceCompatibility = JavaVersion.VERSION_1_7
// targetCompatibility = JavaVersion.VERSION_1_7
//}
println("java.sourceCompatibility = " + java.sourceCompatibility)
println("java.targetCompatibility = " + java.targetCompatibility)
tasks.withType<JavaCompile> {
println("withType<JavaCompile> sourceCompatibility = $sourceCompatibility")
println("withType<JavaCompile> targetCompatibility = $targetCompatibility")
}
tasks.compileJava {
println("tasks.compileJava options.release = " + options.release.get())
}
Output:
java.sourceCompatibility = 11
java.targetCompatibility = 11
withType<JavaCompile> sourceCompatibility = 1.7
withType<JavaCompile> targetCompatibility = 1.7
withType<JavaCompile> sourceCompatibility = 1.7
withType<JavaCompile> targetCompatibility = 1.7
Cannot query the value of task ':common:compileJava' property 'options.release' because it has no value available.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.JETBRAINS
}
}
//tasks.compileJava {
// options.release = 7
//}
//tasks {
// withType<JavaCompile> {
// sourceCompatibility = "1.7"
// targetCompatibility = "1.7"
// }
//}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
println("java.sourceCompatibility = " + java.sourceCompatibility)
println("java.targetCompatibility = " + java.targetCompatibility)
tasks.withType<JavaCompile> {
println("withType<JavaCompile> sourceCompatibility = $sourceCompatibility")
println("withType<JavaCompile> targetCompatibility = $targetCompatibility")
}
tasks.compileJava {
println("tasks.compileJava options.release = " + options.release.get())
}
Output:
java.sourceCompatibility = 1.7
java.targetCompatibility = 1.7
withType<JavaCompile> sourceCompatibility = 1.7
withType<JavaCompile> targetCompatibility = 1.7
withType<JavaCompile> sourceCompatibility = 1.7
withType<JavaCompile> targetCompatibility = 1.7
Cannot query the value of task ':common:compileJava' property 'options.release' because it has no value available.
According to the above
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
Changes probably all possible configurations except only this thing remains uninitialized:
tasks.compileJava {
options.release =
}
But the problem with this way is that:
The sourceCompatibility and targetCompatibility options correspond to the Java compiler options -source and -target. They are considered a legacy mechanism for targeting a specific Java version. However, these options do not protect against the use of APIs introduced in later Java versions. https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_cross_compilation
I see the next ways to fix this:
- Check all possible ways to configure the thing.
- Implemented an IDE inspection to make it impossible not to know about.
- Simply mentioned in the docs here https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin.html
This issue is probably relevant only when compiling for ancient JDKs like for 1.7. It may be necessary if the plugin does some customizations to the JPS or running JUnit tests (in that case a class from the plugin may be run directly on JDK from user's project). Because in all other ways I we could simply use this approach to configure JDK & source level:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.JETBRAINS
}
}
But the above approach does not seem to be available for JDK less than 11 unless we also change the vendor from JvmVendorSpec.JETBRAINS to whatever else provides old JDKs.
Yep, just tested
java {
toolchain {
languageVersion = JavaLanguageVersion.of(7)
}
}
creates a correct variant as well
--------------------------------------------------
Variant intellijPlatformComposedJar
--------------------------------------------------
Capabilities
- org.jetbrains.plugins.template:common:unspecified (default capability)
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.environment = standard-jvm
- org.gradle.jvm.version = 7
- org.gradle.libraryelements = composed-jar
- org.gradle.usage = java-runtime
- org.jetbrains.kotlin.platform.type = jvm
Artifacts
- build/libs/common.jar (artifactType = jar)
But it works only by a coincidence because this way the source level matches the JDK and we also are not using JBR anymore (because there are not old JBRs), which is not critical but not ideal either.
So the recommendation should be:
- Use toolchain of the same version as your desired source level.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(7)
//vendor = JvmVendorSpec.JETBRAINS
}
}
- If the above not possible, add two duplicating configurations:
// This one will configure source level in all possible places in Gradle (tested above)
// However, these options do not protect against the use of APIs introduced in later Java versions.
// Also this is required to fix https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1772 because Intellij Gradle plugin does not look at any other configuration
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
// And another config to fix the lack of API control in the above config:
// The release flag provides guarantees similar to toolchains.
// It validates that the Java sources are not using language features introduced in later Java versions,
// and also that the code does not access APIs from more recent JDKs.
// The bytecode produced by the compiler also corresponds to the requested Java version,
// meaning that the compiled code cannot be executed on older JVMs.
tasks.compileJava {
options.release = 7
}