From fb2a5cf94bef4c0f2ffd07983c08737757bccbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 9 Nov 2020 10:13:34 +0100 Subject: [PATCH] HHH-14315 Add optional support for toolchains to the Gradle build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Yoann Rodière --- build.gradle | 15 +-- documentation/documentation.gradle | 13 ++- gradle.properties | 11 +++ gradle/base-information.gradle | 1 - gradle/java-module.gradle | 82 +++++++++++++--- gradle/published-java-module.gradle | 6 +- hibernate-core/hibernate-core.gradle | 12 +++ ...ernate-integrationtest-java-modules.gradle | 30 +++++- hibernate-osgi/hibernate-osgi.gradle | 2 +- settings.gradle | 97 ++++++++++++++----- .../hibernate-gradle-plugin.gradle | 13 ++- 11 files changed, 226 insertions(+), 56 deletions(-) diff --git a/build.gradle b/build.gradle index 5983ece9abd8..edcd1d48bb18 100644 --- a/build.gradle +++ b/build.gradle @@ -55,11 +55,14 @@ task release { "the fact that subprojects will appropriately define a release task " + "themselves if they have any release-related activities to perform" - // Force to release with JDK 8. Releasing with JDK 11 is not supported yet: - // - the hibernate-orm-modules tests do not run due to an issue with the ASM version currently used by Gradle doFirst { - if ( !JavaVersion.current().isJava8() || !gradle.ext.testedJavaVersionAsEnum.isJava8() ) { - throw new IllegalStateException( "Please use JDK 8 to perform the release." ) + def javaVersionsInUse = [gradle.ext.javaVersions.main.compiler, gradle.ext.javaVersions.main.release, + gradle.ext.javaVersions.test.compiler, gradle.ext.javaVersions.test.release, + gradle.ext.javaVersions.test.launcher].toSet() + // Force to release with JDK 8. Releasing with JDK 11 is not supported yet: + // - the hibernate-orm-modules tests do not run due to an issue with the ASM version currently used by Gradle + if ( javaVersionsInUse != [JavaLanguageVersion.of( 8 )].toSet() ) { + throw new IllegalStateException( "Please use JDK 8 to perform the release. Currently using: ${javaVersionsInUse}" ) } } } @@ -101,8 +104,8 @@ buildScanRecipes { //idea { // project { -// jdkName = baselineJavaVersion -// languageLevel = baselineJavaVersion +// jdkName = gradle.ext.baselineJavaVersion +// languageLevel = gradle.ext.baselineJavaVersion // // vcs = 'Git' // } diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index 54b1c821fcc2..de8beb9e5e5f 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -131,19 +131,26 @@ task aggregateJavadocs(type: Javadoc) { 'https://javaee.github.io/javaee-spec/javadocs/' ] - if ( JavaVersion.current().isJava11Compatible() ) { + if ( gradle.ext.javaVersions.main.compiler.asInt() >= 11 ) { //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 // but after excluding the first two builds; see also specific comments on // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. - System.out.println("Forcing Javadoc in Java 8 compatible mode"); - options.source = project.baselineJavaVersion + logger.lifecycle "Forcing Javadoc in Java 8 compatible mode" + options.source = gradle.ext.baselineJavaVersion } options.addStringOption( 'Xdoclint:none', '-quiet' ) } + if ( gradle.ext.javaToolchainEnabled ) { + // Display version of Java tools + doFirst { + logger.lifecycle "Aggregating javadoc with '${javadocTool.get().metadata.installationPath}'" + } + } + // process each project, building up: // 1) appropriate sources // 2) classpath diff --git a/gradle.properties b/gradle.properties index a502af5921fe..14657c596216 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,13 @@ # Keep system properties in sync with test system properties (java-module.gradle)! org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8 + +# JDK auto-detection is not quite ready yet in Gradle 6.7. +# On Fedora in particular, if you have the package java-1.8.0-openjdk-headless-1.8.0.265.b01-1.fc32.x86_64 installed, +# Gradle will look for the Java binaries in /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.265.b01-1.fc32.x86_64/bin/java +# but it won't find it and will fail. +# It's just a JRE, so it's perfectly normal that the JDK is not present; +# the JRE is under /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.265.b01-1.fc32.x86_64/jre +org.gradle.java.installations.auto-detect=false +# We can't rely on Gradle's auto-download of JDKs as it doesn't support EA releases. +# See https://github.com/gradle/gradle/blob/fc7ea24f3c525d8d12a4346eb0f15976a6be9414/subprojects/platform-jvm/src/main/java/org/gradle/jvm/toolchain/install/internal/AdoptOpenJdkRemoteBinary.java#L114 +org.gradle.java.installations.auto-download=false \ No newline at end of file diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 3affd900a467..d3d00808e179 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -16,7 +16,6 @@ ext { if ( project.hasProperty( 'releaseVersion' ) ) { ormVersion = new HibernateVersion( project.releaseVersion, project ) } - baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index 60e266cccc41..6ea4b56a21e6 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -112,17 +112,64 @@ dependencies { tasks.withType( JavaCompile ) { options.encoding = 'UTF-8' - sourceCompatibility = project.baselineJavaVersion - targetCompatibility = project.baselineJavaVersion } -if ( project.baselineJavaVersion != gradle.ext.testedJavaVersion ) { - logger.info( "Forcing the target bytecode version for test classes to '$gradle.ext.testedJavaVersion'" ) +if ( !gradle.ext.javaToolchainEnabled ) { + tasks.compileJava.configure { + sourceCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.main.release ) + targetCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.main.release ) + } + tasks.compileTestJava.configure { + sourceCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.test.release ) + targetCompatibility = JavaVersion.toVersion( gradle.ext.javaVersions.test.release ) + } +} +else { + // Configure generated bytecode + // "sourceCompatibility" is not supported with toolchains. We have to work around that limitation. + tasks.compileJava.configure { + if ( gradle.ext.javaVersions.main.compiler.asInt() < 9 ) { + options.compilerArgs << '-source' + options.compilerArgs << gradle.ext.javaVersions.main.release.toString() + options.compilerArgs << '-target' + options.compilerArgs << gradle.ext.javaVersions.main.release.toString() + } else { + options.release = gradle.ext.javaVersions.main.release.asInt() + } + } + tasks.compileTestJava.configure { + if ( gradle.ext.javaVersions.test.compiler.asInt() < 9 ) { + options.compilerArgs << '-source' + options.compilerArgs << gradle.ext.javaVersions.test.release.toString() + options.compilerArgs << '-target' + options.compilerArgs << gradle.ext.javaVersions.test.release.toString() + } else { + options.release = gradle.ext.javaVersions.test.release.asInt() + } + } + // Configure version of Java tools + java { + toolchain { + languageVersion = gradle.ext.javaVersions.main.compiler + } + } tasks.compileTestJava { - // For *tests only*, generate bytecode matching the Java version currently in use. - // This allows testing bytecode enhancement on the latest Java versions (13, 14, ...). - targetCompatibility = gradle.ext.testedJavaVersion + javaCompiler = javaToolchains.compilerFor { + languageVersion = gradle.ext.javaVersions.test.compiler + } + } + + // Display version of Java tools + tasks.withType( JavaCompile ).configureEach { + doFirst { + logger.lifecycle "Compiling with '${javaCompiler.get().metadata.installationPath}'" + } + } + tasks.withType( Javadoc ).configureEach { + doFirst { + logger.lifecycle "Generating javadoc with '${javadocTool.get().metadata.installationPath}'" + } } } @@ -166,8 +213,21 @@ if ( ext.toolsJar.exists() ) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Testing +if ( gradle.ext.javaToolchainEnabled ) { + tasks.test { + // Configure version of Java tools + javaLauncher = javaToolchains.launcherFor { + languageVersion = gradle.ext.javaVersions.test.launcher + } + // Display version of Java tools + doFirst { + logger.lifecycle "Testing with '${javaLauncher.get().metadata.installationPath}'" + } + } +} + tasks.withType( Test.class ).all { task -> - if ( JavaVersion.current().isJava9Compatible() ) { + if ( gradle.ext.javaVersions.test.launcher.asInt() >= 9 ) { // Byteman needs this property to be set, https://developer.jboss.org/thread/274997 task.jvmArgs += ["-Djdk.attach.allowAttachSelf=true"] } @@ -217,8 +277,8 @@ test { // Enable the experimental features of ByteBuddy with JDK 15+ test { - if ( Integer.valueOf( gradle.ext.testedJavaVersionAsEnum.getMajorVersion() ) >= 15 ) { - logger.warn( "The version of java that will be tested is not supported by Bytebuddy by default. " + + if ( gradle.ext.javaVersions.test.release.asInt() >= 15 ) { + logger.warn( "The version of Java bytecode that will be tested is not supported by Bytebuddy by default. " + " Setting 'net.bytebuddy.experimental=true'." ) systemProperty 'net.bytebuddy.experimental', true } @@ -344,7 +404,7 @@ task forbiddenApisUnsafe(type: CheckForbiddenApis, dependsOn: compileJava) { classesDirs = project.sourceSets.main.output.classesDirs classpath = project.sourceSets.main.compileClasspath + project.sourceSets.main.runtimeClasspath targetCompatibility = project.forbiddenAPITargetJDKCompatibility - bundledSignatures += "jdk-unsafe-${baselineJavaVersion}".toString() + bundledSignatures += "jdk-unsafe-${gradle.ext.baselineJavaVersion}".toString() // unfortunately we currently have many uses of default Locale implicitly (~370) which need to be fixed // before we can fully enabled this check diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle index 0f627cb63953..ac2a152860f9 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -121,14 +121,14 @@ javadoc { ] tags = [ "apiNote", 'implSpec', 'implNote', 'todo' ] - if ( JavaVersion.current().isJava11Compatible() ) { + if ( gradle.ext.javaVersions.main.compiler.asInt() >= 11 ) { //The need to set `--source 1.8` applies to all JVMs after 11, and also to 11 // but after excluding the first two builds; see also specific comments on // https://bugs.openjdk.java.net/browse/JDK-8212233?focusedCommentId=14245762 // For now, let's be compatible with JDK 11.0.3+. We can improve on it if people // complain they cannot build with JDK 11.0.0, 11.0.1 and 11.0.2. - System.out.println("Forcing Javadoc in Java 8 compatible mode"); - options.source = project.baselineJavaVersion + logger.lifecycle "Forcing Javadoc in Java 8 compatible mode" + options.source = gradle.ext.baselineJavaVersion } options.addStringOption( 'Xdoclint:none', '-quiet' ) diff --git a/hibernate-core/hibernate-core.gradle b/hibernate-core/hibernate-core.gradle index e088f74f3185..cec519d7b7d2 100644 --- a/hibernate-core/hibernate-core.gradle +++ b/hibernate-core/hibernate-core.gradle @@ -274,6 +274,18 @@ task testJavassist(type: Test) { //If you want to ensure that integration tests are run every time when you invoke //this task, uncomment the following line. //outputs.upToDateWhen { false } + + if ( gradle.ext.javaToolchainEnabled ) { + // Configure version of Java tools + javaLauncher = javaToolchains.launcherFor { + languageVersion = gradle.ext.javaVersions.test.launcher + } + + // Display version of Java tools + doFirst { + logger.lifecycle "Testing javassist with '${javaLauncher.get().metadata.installationPath}'" + } + } } check.dependsOn testJavassist diff --git a/hibernate-integrationtest-java-modules/hibernate-integrationtest-java-modules.gradle b/hibernate-integrationtest-java-modules/hibernate-integrationtest-java-modules.gradle index 663e585f4454..7769ca7fb214 100644 --- a/hibernate-integrationtest-java-modules/hibernate-integrationtest-java-modules.gradle +++ b/hibernate-integrationtest-java-modules/hibernate-integrationtest-java-modules.gradle @@ -24,11 +24,31 @@ apply from: rootProject.file( 'gradle/java-module.gradle' ) // so we have to use https://github.com/java9-modularity/gradle-modules-plugin apply plugin: "org.javamodularity.moduleplugin" -// Override -source and -target to the version being tested (11+, since this module is enabled) -ext.baselineJavaVersion = gradle.ext.testedJavaVersion -tasks.withType( JavaCompile ) { - sourceCompatibility = project.baselineJavaVersion - targetCompatibility = project.baselineJavaVersion +// In this module, the "main" code is actually just test code that happens +// to be built independently so as to generate a Java module. +// So, let's override settings for compilation of the main code, just for this particular case. +def testJavaVersions = gradle.ext.javaVersions.test +tasks.compileJava { + if ( !gradle.ext.javaToolchainEnabled ) { + sourceCompatibility = JavaVersion.toVersion( testJavaVersions.release ) + targetCompatibility = JavaVersion.toVersion( testJavaVersions.release ) + } + else { + javaCompiler = javaToolchains.compilerFor { + languageVersion = testJavaVersions.compiler + } + + // Remove JDK8-only options (if any) that are incompatible with options.release + for ( it = options.compilerArgs.listIterator(); it.hasNext(); ) { + if ( it.next() in ['-source', '-target'] ) { + it.remove() + it.next() + it.remove() + } + } + + options.release = testJavaVersions.release.asInt() + } } // Checkstyle fails for module-info diff --git a/hibernate-osgi/hibernate-osgi.gradle b/hibernate-osgi/hibernate-osgi.gradle index 2629e8c6e333..ffd5b1384d90 100644 --- a/hibernate-osgi/hibernate-osgi.gradle +++ b/hibernate-osgi/hibernate-osgi.gradle @@ -35,7 +35,7 @@ ext { * > A bit ironic since the problems we were trying to solve initially only affected JDK11.0.3, * > not JDK11.0.0. */ -if ( JavaVersion.current().isJava11Compatible() ) { +if ( gradle.ext.javaVersions.test.launcher.asInt() >= 11 ) { logger.warn( '[WARN] Skipping all tests for hibernate-osgi due to Karaf/Pax-Exam issues with latest JDK 11' ) test.enabled = false } diff --git a/settings.gradle b/settings.gradle index 48239f1bd84e..22ccea06b7e9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,35 +13,79 @@ if ( !JavaVersion.current().java8Compatible ) { throw new GradleException( "Gradle must be run with Java 8 or later" ) } -// Consume the property 'testedJavaVersion' here and -// set it on gradle.ext so that we can inspect the result in build.gradle. -// We wouldn't be able to do that if we consumed -// the property in base-information.gradle and set it on project.ext. - -if ( hasProperty( 'testedJavaVersion' ) ) { - logger.warn( "[WARN] Targeting Java version '$testedJavaVersion' in tests." ) - gradle.ext.testedJavaVersion = testedJavaVersion - gradle.ext.testedJavaVersionAsEnum = JavaVersion.toVersion( testedJavaVersion ) -} -else { - // We will simply use Gradle's JDK for compilation, tests and javadoc generation. - def major - if ( JavaVersion.current() == JavaVersion.VERSION_HIGHER) { - logger.warn( "Gradle does not support this JDK, because it is too recent; build is likely to fail." + - " To avoid failures, you should specify an older Java version in the 'testedJavaVersion' parameter." + - " Just append the following to your gradle command:" + - " '-PtestedJavaVersion='" ) - // Use a hack to retrieve the major as a string. - // This only works for Java 9+ (we're at least on Java 18 here). - gradle.ext.testedJavaVersion = System.getProperty( 'java.specification.version' ) +gradle.ext.baselineJavaVersion = JavaLanguageVersion.of( 8 ) + +// Gradle does bytecode transformation on tests. +// You can't use bytecode higher than what Gradle supports, even with toolchains. +def GRADLE_MAX_SUPPORTED_BYTECODE_VERSION = 15 + +// If either 'main.jdk.version' or 'test.jdk.version' is set, enable the toolchain and use the selected jdk. +// If only one property is set, the other defaults to the baseline Java version (8). +// Note that when toolchain is enabled, you also need to specify +// the location of the selected jdks +// (auto-download and auto-detect are disabled in gradle.properties). +// +// Example (with SDKMAN): +// ./gradlew build -Ptest.jdk.version=15 \ +// -Porg.gradle.java.installations.paths=$SDKMAN_CANDIDATES_DIR/java/15.0.1-open,$SDKMAN_CANDIDATES_DIR/java/8 +if ( hasProperty( 'main.jdk.version' ) || hasProperty( 'test.jdk.version' ) ) { + // Testing a particular JDK version + // Gradle doesn't support all JDK versions unless we use toolchains + gradle.ext.javaToolchainEnabled = true + gradle.ext.javaVersions = [ + main: [ + compiler: JavaLanguageVersion.of( hasProperty( 'main.jdk.version' ) + ? getProperty( 'main.jdk.version' ) : gradle.ext.baselineJavaVersion.asInt() ), + release: gradle.ext.baselineJavaVersion + ], + test: [ + compiler: JavaLanguageVersion.of( hasProperty( 'test.jdk.version' ) + ? getProperty( 'test.jdk.version' ) : gradle.ext.baselineJavaVersion.asInt() ) + ] + ] + def testCompilerVersion = gradle.ext.javaVersions.test.compiler + if ( testCompilerVersion.asInt() > GRADLE_MAX_SUPPORTED_BYTECODE_VERSION ) { + logger.warn( "[WARN] Gradle does not support bytecode version '${testCompilerVersion}'." + + " Forcing test bytecode to version ${GRADLE_MAX_SUPPORTED_BYTECODE_VERSION}." ) + gradle.ext.javaVersions.test.release = JavaLanguageVersion.of( GRADLE_MAX_SUPPORTED_BYTECODE_VERSION ) } else { - gradle.ext.testedJavaVersion = JavaVersion.current().getMajorVersion() + gradle.ext.javaVersions.test.release = testCompilerVersion + } + gradle.ext.javaVersions.test.launcher = testCompilerVersion +} +else { + // Not testing a particular JDK version: we will use the same JDK used to run Gradle. + // We disable toolchains for convenience, so that anyone can just run the build with their own JDK + // without any additional options and without downloading the whole JDK. + gradle.ext.javaToolchainEnabled = false + def gradleJdkVersion = JavaLanguageVersion.of( JavaVersion.current().getMajorVersion() ) + if ( gradleJdkVersion.asInt() > GRADLE_MAX_SUPPORTED_BYTECODE_VERSION ) { + logger.warn( "[WARN] Gradle does not support this JDK, because it is too recent; build is likely to fail." + + " To avoid failures, you should use an older Java version when running Gradle, and rely on toolchains." + + " To that end, specify the version of Java you want to run tests with using property 'test.jdk.version'," + + " and specify the path to JDK8 *and* a JDK of the test version using property 'org.gradle.java.installations.paths'." + + " Example:" + + "./gradlew build -Ptest.jdk.version=15 -Porg.gradle.java.installations.paths=\$SDKMAN_CANDIDATES_DIR/java/15.0.1-open,\$SDKMAN_CANDIDATES_DIR/java/8" ) } - gradle.ext.testedJavaVersionAsEnum = JavaVersion.current() + gradle.ext.javaVersions = [ + main: [ + compiler: gradleJdkVersion, + release: gradle.ext.baselineJavaVersion + ], + test: [ + compiler: gradleJdkVersion, + release: JavaLanguageVersion.of( + Math.min( GRADLE_MAX_SUPPORTED_BYTECODE_VERSION, gradleJdkVersion.asInt() ) ), + launcher: gradleJdkVersion + ] + ] } +logger.lifecycle "Java versions for main code: " + gradle.ext.javaVersions.main +logger.lifecycle "Java versions for tests: " + gradle.ext.javaVersions.test + include 'hibernate-core' include 'hibernate-entitymanager' include 'hibernate-testing' @@ -67,7 +111,12 @@ include 'hibernate-micrometer' include 'hibernate-orm-modules' include 'hibernate-graalvm' -if ( JavaVersion.current().isJava11Compatible() ) { +// The plugin used to generate Java modules was compiled using JDK11. +// This means even with toolchains, Gradle needs to be run with Java 11+ in order to run Java modules ITs. +// We might be able to get rid of that limitation by relying on Gradle's built-in support for Java modules, +// but I (Yoann) tried and failed to make it work. +// See https://docs.gradle.org/current/samples/sample_java_modules_multi_project.html +if ( JavaVersion.current().isJava11Compatible() && gradle.ext.javaVersions.test.release.asInt() >= 9 ) { include 'hibernate-integrationtest-java-modules' } else { diff --git a/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle b/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle index f7f018bcd832..9ce68edeef5a 100644 --- a/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle +++ b/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle @@ -24,6 +24,15 @@ dependencies { tasks.withType( GroovyCompile ) { options.encoding = 'UTF-8' - sourceCompatibility = project.baselineJavaVersion - targetCompatibility = project.baselineJavaVersion +} + +if ( !gradle.ext.javaToolchainEnabled ) { + tasks.withType( GroovyCompile ) { + sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) + targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) + } +} +else { + logger.warn( "[WARN] Toolchains are not yet supported for Groovy compilation." + + " Using the JDK that runs Gradle for Groovy compilation." ) }