Browse Source

Introduce Gradle Toolchain support in build

Prior to this commit, the Spring Framework build would rely on
setting a custom Java HOME for building all sources and tests
with that JDK.

This approach is not flexible enough, since we would be testing
the source compatibility against a recent JDK, but not a common
case experienced by the community: compiling and running
application code with a recent JDK and the official, JDK8-based
Framework artifacts.
This method is also limiting our choice of JDKs to the ones
currently supported by Gradle itself.

This commit introduces the support of Gradle JVM Toolchains in
the Spring Framework build.

We can now select a specific JDK for compiling the main
SourceSets (Java, Groovy and Kotlin) and another one for
compiling and running the test SourceSets:

`./gradlew check -PmainToolChain=8 -PtestToolchain=15`

Gradle will automatically find the JDKs present on the host or
download one automcatically. You can find out about the ones
installed on your host using:

`./gradlew -q javaToolchains`

Finally, this commit also refactors the CI infrastructure to:

* only have a single CI image (with all the supported JDKs)
* use this new feature to compile with JDK8 but test it
against JDK11 and JDK15.

Closes gh-25787
pull/26684/head
Brian Clozel 4 years ago
parent
commit
a8d553218c
  1. 4
      build.gradle
  2. 10
      buildSrc/README.md
  3. 25
      buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
  4. 8
      ci/images/ci-image-jdk11/Dockerfile
  5. 8
      ci/images/ci-image-jdk15/Dockerfile
  6. 5
      ci/images/ci-image/Dockerfile
  7. 17
      ci/images/setup.sh
  8. 34
      ci/pipeline.yml
  9. 3
      ci/scripts/check-project.sh
  10. 2
      ci/tasks/check-project.yml
  11. 80
      gradle/custom-java-home.gradle
  12. 1
      gradle/spring-module.gradle
  13. 127
      gradle/toolchains.gradle
  14. 8
      settings.gradle
  15. 2
      spring-beans/spring-beans.gradle

4
build.gradle

@ -310,14 +310,13 @@ configure([rootProject] + javaProjects) { project -> @@ -310,14 +310,13 @@ configure([rootProject] + javaProjects) { project ->
apply plugin: "java-test-fixtures"
apply plugin: "checkstyle"
apply plugin: 'org.springframework.build.compile'
apply from: "${rootDir}/gradle/custom-java-home.gradle"
apply from: "${rootDir}/gradle/toolchains.gradle"
apply from: "${rootDir}/gradle/ide.gradle"
pluginManager.withPlugin("kotlin") {
apply plugin: "org.jetbrains.dokka"
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
languageVersion = "1.3"
apiVersion = "1.3"
freeCompilerArgs = ["-Xjsr305=strict"]
@ -326,7 +325,6 @@ configure([rootProject] + javaProjects) { project -> @@ -326,7 +325,6 @@ configure([rootProject] + javaProjects) { project ->
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = ["-Xjsr305=strict"]
}
}

10
buildSrc/README.md

@ -8,16 +8,10 @@ They are declared in the `build.gradle` file in this folder. @@ -8,16 +8,10 @@ They are declared in the `build.gradle` file in this folder.
### Compiler conventions
The `org.springframework.build.compile` plugin applies the Java compiler conventions to the build.
By default, the build compiles sources with Java `1.8` source and target compatibility.
You can test a different source compatibility version on the CLI with a project property like:
```
./gradlew test -PjavaSourceVersion=11
```
## Build Plugins
## Optional dependencies
### Optional dependencies
The `org.springframework.build.optional-dependencies` plugin creates a new `optional`
Gradle configuration - it adds the dependencies to the project's compile and runtime classpath
@ -25,7 +19,7 @@ but doesn't affect the classpath of dependent projects. @@ -25,7 +19,7 @@ but doesn't affect the classpath of dependent projects.
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
configurations are preferred.
## API Diff
### API Diff
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root

25
buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,31 +20,21 @@ import java.util.ArrayList; @@ -20,31 +20,21 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.compile.JavaCompile;
/**
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
* <p>One can override the default Java source compatibility version
* with a dedicated property on the CLI: {@code "./gradlew test -PjavaSourceVersion=11"}.
*
* @author Brian Clozel
* @author Sam Brannen
*/
public class CompilerConventionsPlugin implements Plugin<Project> {
/**
* The project property that can be used to switch the Java source
* compatibility version for building source and test classes.
*/
public static final String JAVA_SOURCE_VERSION_PROPERTY = "javaSourceVersion";
public static final JavaVersion DEFAULT_COMPILER_VERSION = JavaVersion.VERSION_1_8;
private static final List<String> COMPILER_ARGS;
private static final List<String> TEST_COMPILER_ARGS;
@ -69,7 +59,7 @@ public class CompilerConventionsPlugin implements Plugin<Project> { @@ -69,7 +59,7 @@ public class CompilerConventionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> applyJavaCompileConventions(project));
project.getPlugins().withType(JavaLibraryPlugin.class, javaPlugin -> applyJavaCompileConventions(project));
}
/**
@ -79,15 +69,6 @@ public class CompilerConventionsPlugin implements Plugin<Project> { @@ -79,15 +69,6 @@ public class CompilerConventionsPlugin implements Plugin<Project> {
*/
private void applyJavaCompileConventions(Project project) {
JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class);
if (project.hasProperty(JAVA_SOURCE_VERSION_PROPERTY)) {
JavaVersion javaSourceVersion = JavaVersion.toVersion(project.property(JAVA_SOURCE_VERSION_PROPERTY));
java.setSourceCompatibility(javaSourceVersion);
}
else {
java.setSourceCompatibility(DEFAULT_COMPILER_VERSION);
}
java.setTargetCompatibility(DEFAULT_COMPILER_VERSION);
project.getTasks().withType(JavaCompile.class)
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
.forEach(compileTask -> {

8
ci/images/ci-image-jdk11/Dockerfile

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java11
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH

8
ci/images/ci-image-jdk15/Dockerfile

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
FROM ubuntu:focal-20210119
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java15
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH

5
ci/images/ci-image/Dockerfile

@ -4,5 +4,8 @@ ADD setup.sh /setup.sh @@ -4,5 +4,8 @@ ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh java8
ENV JAVA_HOME /opt/openjdk
ENV JAVA_HOME /opt/openjdk/java8
ENV JDK11 /opt/openjdk/java11
ENV JDK15 /opt/openjdk/java15
ENV PATH $JAVA_HOME/bin:$PATH

17
ci/images/setup.sh

@ -19,13 +19,20 @@ curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/ @@ -19,13 +19,20 @@ curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/
###########################################################
# JAVA
###########################################################
JDK_URL=$( ./get-jdk-url.sh $1 )
mkdir -p /opt/openjdk
cd /opt/openjdk
curl -L ${JDK_URL} | tar zx --strip-components=1
test -f /opt/openjdk/bin/java
test -f /opt/openjdk/bin/javac
pushd /opt/openjdk > /dev/null
for jdk in java8 java11 java15
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
pushd $jdk > /dev/null
curl -L ${JDK_URL} | tar zx --strip-components=1
test -f bin/java
test -f bin/javac
popd > /dev/null
done
popd
###########################################################
# GRADLE ENTERPRISE

34
ci/pipeline.yml

@ -85,18 +85,6 @@ resources: @@ -85,18 +85,6 @@ resources:
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
- name: ci-image-jdk11
type: docker-image
icon: docker
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci-jdk11
- name: ci-image-jdk15
type: docker-image
icon: docker
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci-jdk15
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@ -161,14 +149,6 @@ jobs: @@ -161,14 +149,6 @@ jobs:
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/ci-image/Dockerfile
- put: ci-image-jdk11
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/ci-image-jdk11/Dockerfile
- put: ci-image-jdk15
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/ci-image-jdk15/Dockerfile
- name: build
serial: true
public: true
@ -227,7 +207,7 @@ jobs: @@ -227,7 +207,7 @@ jobs:
serial: true
public: true
plan:
- get: ci-image-jdk11
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
@ -235,8 +215,11 @@ jobs: @@ -235,8 +215,11 @@ jobs:
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image-jdk11
image: ci-image
file: git-repo/ci/tasks/check-project.yml
params:
MAIN_TOOLCHAIN: 8
TEST_TOOLCHAIN: 11
<<: *build-project-task-params
on_failure:
do:
@ -251,7 +234,7 @@ jobs: @@ -251,7 +234,7 @@ jobs:
serial: true
public: true
plan:
- get: ci-image-jdk15
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
@ -259,8 +242,11 @@ jobs: @@ -259,8 +242,11 @@ jobs:
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image-jdk15
image: ci-image
file: git-repo/ci/tasks/check-project.yml
params:
MAIN_TOOLCHAIN: 8
TEST_TOOLCHAIN: 15
<<: *build-project-task-params
on_failure:
do:

3
ci/scripts/check-project.sh

@ -4,5 +4,6 @@ set -e @@ -4,5 +4,6 @@ set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Dorg.gradle.java.installations.fromEnv=JDK11,JDK15 \
-PmainToolchain=$MAIN_TOOLCHAIN -PtestToolchain=$TEST_TOOLCHAIN --no-daemon --max-workers=4 check
popd > /dev/null

2
ci/tasks/check-project.yml

@ -10,6 +10,8 @@ caches: @@ -10,6 +10,8 @@ caches:
params:
BRANCH:
CI: true
MAIN_TOOLCHAIN:
TEST_TOOLCHAIN:
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:

80
gradle/custom-java-home.gradle

@ -1,80 +0,0 @@ @@ -1,80 +0,0 @@
// -----------------------------------------------------------------------------
//
// This script adds support for the following two JVM system properties
// that control the build for alternative JDKs (i.e., a JDK other than
// the one used to launch the Gradle process).
//
// - customJavaHome: absolute path to the alternate JDK installation to
// use to compile Java code and execute tests. This system property
// is also used in spring-oxm.gradle to determine whether JiBX is
// supported.
//
// - customJavaSourceVersion: Java version supplied to the `--release`
// command line flag to control the Java source and target
// compatibility version. Supported versions include 9 or higher.
// Do not set this system property if Java 8 should be used.
//
// Examples:
//
// ./gradlew -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home test
//
// ./gradlew --no-build-cache -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home test
//
// ./gradlew -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home -DcustomJavaSourceVersion=14 test
//
//
// Credits: inspired by work from Marc Philipp and Stephane Nicoll
//
// -----------------------------------------------------------------------------
import org.gradle.internal.os.OperatingSystem
// import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
def customJavaHome = System.getProperty("customJavaHome")
if (customJavaHome) {
def customJavaHomeDir = new File(customJavaHome)
def customJavaSourceVersion = System.getProperty("customJavaSourceVersion")
tasks.withType(JavaCompile) {
logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHomeDir)
options.forkOptions.javaHome = customJavaHomeDir
inputs.property("customJavaHome", customJavaHome)
if (customJavaSourceVersion) {
options.compilerArgs += [ "--release", customJavaSourceVersion]
inputs.property("customJavaSourceVersion", customJavaSourceVersion)
}
}
tasks.withType(GroovyCompile) {
logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHomeDir)
options.forkOptions.javaHome = customJavaHomeDir
inputs.property("customJavaHome", customJavaHome)
if (customJavaSourceVersion) {
options.compilerArgs += [ "--release", customJavaSourceVersion]
inputs.property("customJavaSourceVersion", customJavaSourceVersion)
}
}
/*
tasks.withType(KotlinJvmCompile) {
logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHome)
kotlinOptions.jdkHome = customJavaHomeDir
inputs.property("customJavaHome", customJavaHome)
}
*/
tasks.withType(Test) {
def javaExecutable = customJavaHome + "/bin/java"
if (OperatingSystem.current().isWindows()) {
javaExecutable += ".exe"
}
logger.info("Java executable for " + it.name + " task in " + project.name + ": " + javaExecutable)
executable = javaExecutable
inputs.property("customJavaHome", customJavaHome)
if (customJavaSourceVersion) {
inputs.property("customJavaSourceVersion", customJavaSourceVersion)
}
}
}

1
gradle/spring-module.gradle

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
apply plugin: 'java-library'
apply plugin: 'org.springframework.build.compile'
apply plugin: 'org.springframework.build.optional-dependencies'
// Uncomment the following for Shadow support in the jmhJar block.

127
gradle/toolchains.gradle

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
/**
* Apply the JVM Toolchain conventions
* See https://docs.gradle.org/current/userguide/toolchains.html
*
* One can choose the toolchain to use for compiling the MAIN sources and/or compiling
* and running the TEST sources. These options apply to Java, Kotlin and Groovy sources
* when available.
* {@code "./gradlew check -PmainToolchain=8 -PtestToolchain=11"} will use:
* <ul>
* <li>a JDK8 toolchain for compiling the main SourceSet
* <li>a JDK11 toolchain for compiling and running the test SourceSet
* </ul>
*
* Gradle will automatically detect JDK distributions in well-known locations.
* The following command will list the detected JDKs on the host.
* {@code
* $ ./gradlew -q javaToolchains
* }
*
* We can also configure ENV variables and let Gradle know about them:
* {@code
* $ echo JDK11
* /opt/openjdk/java11
* $ echo JDK15
* /opt/openjdk/java15
* $ ./gradlew -Dorg.gradle.java.installations.fromEnv=JDK11,JDK15 check
* }
*
* @author Brian Clozel
*/
def mainToolchain = 'mainToolchain'
def testToolchain = 'testToolchain'
plugins.withType(JavaPlugin) {
// Configure the Java Toolchain if the 'mainToolchain' property is defined
if (project.hasProperty(mainToolchain)) {
def mainLanguageVersion = JavaLanguageVersion.of(project.property(mainToolchain).toString())
java {
toolchain {
languageVersion = mainLanguageVersion
}
}
}
else {
// Fallback to JDK8
java {
sourceCompatibility = JavaVersion.VERSION_1_8
}
}
// Configure a specific Java Toolchain for compiling and running tests if the 'testToolchain' property is defined
if (project.hasProperty(testToolchain)) {
def testLanguageVersion = JavaLanguageVersion.of(project.property(testToolchain).toString());
tasks.withType(JavaCompile).matching { it.name.contains("Test") }.configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = testLanguageVersion
}
}
tasks.withType(Test).configureEach{
javaLauncher = javaToolchains.launcherFor {
languageVersion = testLanguageVersion
}
}
}
}
plugins.withType(GroovyPlugin) {
// Fallback to JDK8
if (!project.hasProperty(mainToolchain)) {
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_1_8
}
}
}
// Configure the Kotlin compiler if the 'mainToolchain' property is defined
pluginManager.withPlugin("kotlin") {
if (project.hasProperty(mainToolchain)) {
def mainLanguageVersion = JavaLanguageVersion.of(project.property(mainToolchain).toString());
def compiler = javaToolchains.compilerFor {
languageVersion = mainLanguageVersion
}
// See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm
def javaVersion = mainLanguageVersion.toString() == '8' ? '1.8' : mainLanguageVersion.toString()
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
// Compile the test classes with the same version, 'testToolchain' will override if defined
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
}
else {
compileKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
}
if (project.hasProperty(testToolchain)) {
def testLanguageVersion = JavaLanguageVersion.of(project.property(testToolchain).toString());
def compiler = javaToolchains.compilerFor {
languageVersion = testLanguageVersion
}
// See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm
def javaVersion = testLanguageVersion.toString() == '8' ? '1.8' : testLanguageVersion.toString()
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
}
else {
compileTestKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
}
}

8
settings.gradle

@ -45,11 +45,11 @@ rootProject.children.each {project -> @@ -45,11 +45,11 @@ rootProject.children.each {project ->
settings.gradle.projectsLoaded {
gradleEnterprise {
buildScan {
if (settings.gradle.rootProject.hasProperty('customJavaHome')) {
value("Custom JAVA_HOME", settings.gradle.rootProject.getProperty('customJavaHome'))
if (settings.gradle.rootProject.hasProperty('mainToolchain')) {
value("Main toolchain", 'JDK' + settings.gradle.rootProject.getProperty('mainToolchain'))
}
if (settings.gradle.rootProject.hasProperty('customJavaSourceVersion')) {
value("Custom Java Source Version", settings.gradle.rootProject.getProperty('customJavaSourceVersion'))
if (settings.gradle.rootProject.hasProperty('testToolchain')) {
value("Test toolchain", 'JDK' + settings.gradle.rootProject.getProperty('testToolchain'))
}
File buildDir = settings.gradle.rootProject.getBuildDir()
buildDir.mkdirs()

2
spring-beans/spring-beans.gradle

@ -23,8 +23,6 @@ sourceSets { @@ -23,8 +23,6 @@ sourceSets {
}
compileGroovy {
sourceCompatibility = 1.8
targetCompatibility = 1.8
options.compilerArgs += "-Werror"
}

Loading…
Cancel
Save