Revisit compiler configuration in project build
This commit revisit the build configuration to enforce the following: * A single Java toolchain is used consistently with a recent Java version (here, Java 23) and language level * the main source is compiled with the Java 17 "-release" target * Multi-Release classes are compiled with their respective "-release" target. For now, only "spring-core" ships Java 21 variants. Closes gh-34507
This commit is contained in:
parent
382caac37c
commit
68e9460e9b
|
@ -3,12 +3,9 @@ plugins {
|
||||||
// kotlinVersion is managed in gradle.properties
|
// kotlinVersion is managed in gradle.properties
|
||||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||||
id 'org.jetbrains.dokka' version '1.9.20'
|
id 'org.jetbrains.dokka' version '1.9.20'
|
||||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
|
||||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||||
id 'de.undercouch.download' version '5.4.0'
|
|
||||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||||
id 'me.champeau.mrjar' version '0.1.1'
|
|
||||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +61,6 @@ configure([rootProject] + javaProjects) { project ->
|
||||||
apply plugin: "java"
|
apply plugin: "java"
|
||||||
apply plugin: "java-test-fixtures"
|
apply plugin: "java-test-fixtures"
|
||||||
apply plugin: 'org.springframework.build.conventions'
|
apply plugin: 'org.springframework.build.conventions'
|
||||||
apply from: "${rootDir}/gradle/toolchains.gradle"
|
|
||||||
apply from: "${rootDir}/gradle/ide.gradle"
|
apply from: "${rootDir}/gradle/ide.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -33,6 +33,25 @@ but doesn't affect the classpath of dependent projects.
|
||||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||||
configurations are preferred.
|
configurations are preferred.
|
||||||
|
|
||||||
|
### MultiRelease Jar
|
||||||
|
|
||||||
|
The `org.springframework.build.multiReleaseJar` plugin configures the project with MultiRelease JAR support.
|
||||||
|
It creates a new SourceSet and dedicated tasks for each Java variant considered.
|
||||||
|
This can be configured with the DSL, by setting a list of Java variants to configure:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
plugins {
|
||||||
|
id 'org.springframework.build.multiReleaseJar'
|
||||||
|
}
|
||||||
|
|
||||||
|
multiRelease {
|
||||||
|
releaseVersions 21, 24
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note, Java classes will be compiled with the toolchain pre-configured by the project, assuming that its
|
||||||
|
Java language version is equal or higher than all variants we consider. Each compilation task will only
|
||||||
|
set the "-release" compilation option accordingly to produce the expected bytecode version.
|
||||||
|
|
||||||
### RuntimeHints Java Agent
|
### RuntimeHints Java Agent
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,13 @@ ext {
|
||||||
dependencies {
|
dependencies {
|
||||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||||
implementation "com.tngtech.archunit:archunit:1.3.0"
|
implementation "com.tngtech.archunit:archunit:1.4.0"
|
||||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
|
||||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||||
|
|
||||||
|
testImplementation("org.assertj:assertj-core:${assertjVersion}")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter:${junitJupiterVersion}")
|
||||||
}
|
}
|
||||||
|
|
||||||
gradlePlugin {
|
gradlePlugin {
|
||||||
|
@ -40,6 +43,10 @@ gradlePlugin {
|
||||||
id = "org.springframework.build.localdev"
|
id = "org.springframework.build.localdev"
|
||||||
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
|
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
|
||||||
}
|
}
|
||||||
|
multiReleasePlugin {
|
||||||
|
id = "org.springframework.build.multiReleaseJar"
|
||||||
|
implementationClass = "org.springframework.build.multirelease.MultiReleaseJarPlugin"
|
||||||
|
}
|
||||||
optionalDependenciesPlugin {
|
optionalDependenciesPlugin {
|
||||||
id = "org.springframework.build.optional-dependencies"
|
id = "org.springframework.build.optional-dependencies"
|
||||||
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
|
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
|
||||||
|
@ -50,3 +57,9 @@ gradlePlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
jar.dependsOn check
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
javaFormatVersion=0.0.42
|
javaFormatVersion=0.0.42
|
||||||
|
junitJupiterVersion=5.11.4
|
||||||
|
assertjVersion=3.27.3
|
|
@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**");
|
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/**");
|
||||||
List<String> buildFolders = List.of("bin", "build", "out");
|
List<String> buildFolders = List.of("bin", "build", "out");
|
||||||
project.allprojects(subproject -> {
|
project.allprojects(subproject -> {
|
||||||
Path rootPath = project.getRootDir().toPath();
|
Path rootPath = project.getRootDir().toPath();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2024 the original author or authors.
|
* Copyright 2002-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.build;
|
package org.springframework.build;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.gradle.api.Plugin;
|
import org.gradle.api.Plugin;
|
||||||
|
@ -42,8 +41,18 @@ public class JavaConventions {
|
||||||
|
|
||||||
private static final List<String> TEST_COMPILER_ARGS;
|
private static final List<String> TEST_COMPILER_ARGS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Java version we should use as the JVM baseline for building the project
|
||||||
|
*/
|
||||||
|
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(23);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Java version we should use as the baseline for the compiled bytecode (the "-release" compiler argument)
|
||||||
|
*/
|
||||||
|
private static final JavaLanguageVersion DEFAULT_RELEASE_VERSION = JavaLanguageVersion.of(17);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
List<String> commonCompilerArgs = Arrays.asList(
|
List<String> commonCompilerArgs = List.of(
|
||||||
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
|
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
|
||||||
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
|
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
|
||||||
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
|
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
|
||||||
|
@ -51,37 +60,45 @@ public class JavaConventions {
|
||||||
);
|
);
|
||||||
COMPILER_ARGS = new ArrayList<>();
|
COMPILER_ARGS = new ArrayList<>();
|
||||||
COMPILER_ARGS.addAll(commonCompilerArgs);
|
COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||||
COMPILER_ARGS.addAll(Arrays.asList(
|
COMPILER_ARGS.addAll(List.of(
|
||||||
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
|
||||||
"-Xlint:unchecked", "-Werror"
|
"-Xlint:unchecked", "-Werror"
|
||||||
));
|
));
|
||||||
TEST_COMPILER_ARGS = new ArrayList<>();
|
TEST_COMPILER_ARGS = new ArrayList<>();
|
||||||
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
|
||||||
TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
TEST_COMPILER_ARGS.addAll(List.of("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
|
||||||
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
"-Xlint:-deprecation", "-Xlint:-unchecked"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply(Project project) {
|
public void apply(Project project) {
|
||||||
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
|
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> {
|
||||||
|
applyToolchainConventions(project);
|
||||||
|
applyJavaCompileConventions(project);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the common Java compiler options for main sources, test fixture sources, and
|
* Configure the Toolchain support for the project.
|
||||||
|
* @param project the current project
|
||||||
|
*/
|
||||||
|
private static void applyToolchainConventions(Project project) {
|
||||||
|
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||||
|
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
||||||
|
toolchain.getLanguageVersion().set(DEFAULT_LANGUAGE_VERSION);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the common Java compiler options for main sources, test fixture sources, and
|
||||||
* test sources.
|
* test sources.
|
||||||
* @param project the current project
|
* @param project the current project
|
||||||
*/
|
*/
|
||||||
private void applyJavaCompileConventions(Project project) {
|
private void applyJavaCompileConventions(Project project) {
|
||||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
|
||||||
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
|
||||||
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(23));
|
|
||||||
});
|
|
||||||
SpringFrameworkExtension frameworkExtension = project.getExtensions().getByType(SpringFrameworkExtension.class);
|
|
||||||
project.afterEvaluate(p -> {
|
project.afterEvaluate(p -> {
|
||||||
p.getTasks().withType(JavaCompile.class)
|
p.getTasks().withType(JavaCompile.class)
|
||||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||||
.forEach(compileTask -> {
|
.forEach(compileTask -> {
|
||||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||||
compileTask.getOptions().getCompilerArgumentProviders().add(frameworkExtension.asArgumentProvider());
|
|
||||||
compileTask.getOptions().setEncoding("UTF-8");
|
compileTask.getOptions().setEncoding("UTF-8");
|
||||||
setJavaRelease(compileTask);
|
setJavaRelease(compileTask);
|
||||||
});
|
});
|
||||||
|
@ -90,7 +107,6 @@ public class JavaConventions {
|
||||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||||
.forEach(compileTask -> {
|
.forEach(compileTask -> {
|
||||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||||
compileTask.getOptions().getCompilerArgumentProviders().add(frameworkExtension.asArgumentProvider());
|
|
||||||
compileTask.getOptions().setEncoding("UTF-8");
|
compileTask.getOptions().setEncoding("UTF-8");
|
||||||
setJavaRelease(compileTask);
|
setJavaRelease(compileTask);
|
||||||
});
|
});
|
||||||
|
@ -98,8 +114,12 @@ public class JavaConventions {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We should pick the {@link #DEFAULT_RELEASE_VERSION} for all compiled classes,
|
||||||
|
* unless the current task is compiling multi-release JAR code with a higher version.
|
||||||
|
*/
|
||||||
private void setJavaRelease(JavaCompile task) {
|
private void setJavaRelease(JavaCompile task) {
|
||||||
int defaultVersion = 17;
|
int defaultVersion = DEFAULT_RELEASE_VERSION.asInt();
|
||||||
int releaseVersion = defaultVersion;
|
int releaseVersion = defaultVersion;
|
||||||
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
|
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
|
||||||
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
|
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2024 the original author or authors.
|
* Copyright 2002-2025 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -21,6 +21,8 @@ import java.util.List;
|
||||||
|
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.provider.Property;
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile;
|
||||||
|
import org.gradle.api.tasks.testing.Test;
|
||||||
import org.gradle.process.CommandLineArgumentProvider;
|
import org.gradle.process.CommandLineArgumentProvider;
|
||||||
|
|
||||||
public class SpringFrameworkExtension {
|
public class SpringFrameworkExtension {
|
||||||
|
@ -29,13 +31,18 @@ public class SpringFrameworkExtension {
|
||||||
|
|
||||||
public SpringFrameworkExtension(Project project) {
|
public SpringFrameworkExtension(Project project) {
|
||||||
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
|
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
|
||||||
|
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile ->
|
||||||
|
javaCompile.getOptions().getCompilerArgumentProviders().add(asArgumentProvider()));
|
||||||
|
project.getTasks().withType(Test.class).configureEach(test ->
|
||||||
|
test.getJvmArgumentProviders().add(asArgumentProvider()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Property<Boolean> getEnableJavaPreviewFeatures() {
|
public Property<Boolean> getEnableJavaPreviewFeatures() {
|
||||||
return this.enableJavaPreviewFeatures;
|
return this.enableJavaPreviewFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandLineArgumentProvider asArgumentProvider() {
|
private CommandLineArgumentProvider asArgumentProvider() {
|
||||||
return () -> {
|
return () -> {
|
||||||
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
|
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
|
||||||
return List.of("--enable-preview");
|
return List.of("--enable-preview");
|
||||||
|
|
|
@ -64,8 +64,6 @@ class TestConventions {
|
||||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||||
"-Xshare:off"
|
"-Xshare:off"
|
||||||
);
|
);
|
||||||
test.getJvmArgumentProviders().add(project.getExtensions()
|
|
||||||
.getByType(SpringFrameworkExtension.class).asArgumentProvider());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureTestRetryPlugin(Project project, Test test) {
|
private void configureTestRetryPlugin(Project project, Test test) {
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.build.multirelease;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.gradle.api.artifacts.Configuration;
|
||||||
|
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||||
|
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||||
|
import org.gradle.api.attributes.LibraryElements;
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.java.archives.Attributes;
|
||||||
|
import org.gradle.api.model.ObjectFactory;
|
||||||
|
import org.gradle.api.tasks.SourceSet;
|
||||||
|
import org.gradle.api.tasks.SourceSetContainer;
|
||||||
|
import org.gradle.api.tasks.TaskContainer;
|
||||||
|
import org.gradle.api.tasks.TaskProvider;
|
||||||
|
import org.gradle.api.tasks.bundling.Jar;
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile;
|
||||||
|
import org.gradle.api.tasks.testing.Test;
|
||||||
|
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Cedric Champeau
|
||||||
|
* @author Brian Clozel
|
||||||
|
*/
|
||||||
|
public abstract class MultiReleaseExtension {
|
||||||
|
private final TaskContainer tasks;
|
||||||
|
private final SourceSetContainer sourceSets;
|
||||||
|
private final DependencyHandler dependencies;
|
||||||
|
private final ObjectFactory objects;
|
||||||
|
private final ConfigurationContainer configurations;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MultiReleaseExtension(SourceSetContainer sourceSets,
|
||||||
|
ConfigurationContainer configurations,
|
||||||
|
TaskContainer tasks,
|
||||||
|
DependencyHandler dependencies,
|
||||||
|
ObjectFactory objectFactory) {
|
||||||
|
this.sourceSets = sourceSets;
|
||||||
|
this.configurations = configurations;
|
||||||
|
this.tasks = tasks;
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
this.objects = objectFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseVersions(int... javaVersions) {
|
||||||
|
releaseVersions("src/main/", "src/test/", javaVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseVersions(String mainSourceDirectory, String testSourceDirectory, int... javaVersions) {
|
||||||
|
for (int javaVersion : javaVersions) {
|
||||||
|
addLanguageVersion(javaVersion, mainSourceDirectory, testSourceDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLanguageVersion(int javaVersion, String mainSourceDirectory, String testSourceDirectory) {
|
||||||
|
String javaN = "java" + javaVersion;
|
||||||
|
|
||||||
|
SourceSet langSourceSet = sourceSets.create(javaN, srcSet -> srcSet.getJava().srcDir(mainSourceDirectory + javaN));
|
||||||
|
SourceSet testSourceSet = sourceSets.create(javaN + "Test", srcSet -> srcSet.getJava().srcDir(testSourceDirectory + javaN));
|
||||||
|
SourceSet sharedSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||||
|
SourceSet sharedTestSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
|
||||||
|
|
||||||
|
FileCollection mainClasses = objects.fileCollection().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs());
|
||||||
|
dependencies.add(javaN + "Implementation", mainClasses);
|
||||||
|
|
||||||
|
tasks.named(langSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||||
|
task.getOptions().getRelease().set(javaVersion)
|
||||||
|
);
|
||||||
|
tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
|
||||||
|
task.getOptions().getRelease().set(javaVersion)
|
||||||
|
);
|
||||||
|
|
||||||
|
TaskProvider<Test> testTask = createTestTask(javaVersion, testSourceSet, sharedTestSourceSet, langSourceSet, sharedSourceSet);
|
||||||
|
tasks.named("check", task -> task.dependsOn(testTask));
|
||||||
|
|
||||||
|
configureMultiReleaseJar(javaVersion, langSourceSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskProvider<Test> createTestTask(int javaVersion, SourceSet testSourceSet, SourceSet sharedTestSourceSet, SourceSet langSourceSet, SourceSet sharedSourceSet) {
|
||||||
|
Configuration testImplementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
|
||||||
|
testImplementation.extendsFrom(configurations.getByName(sharedTestSourceSet.getImplementationConfigurationName()));
|
||||||
|
Configuration testCompileOnly = configurations.getByName(testSourceSet.getCompileOnlyConfigurationName());
|
||||||
|
testCompileOnly.extendsFrom(configurations.getByName(sharedTestSourceSet.getCompileOnlyConfigurationName()));
|
||||||
|
testCompileOnly.getDependencies().add(dependencies.create(langSourceSet.getOutput().getClassesDirs()));
|
||||||
|
testCompileOnly.getDependencies().add(dependencies.create(sharedSourceSet.getOutput().getClassesDirs()));
|
||||||
|
|
||||||
|
Configuration testRuntimeClasspath = configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName());
|
||||||
|
// so here's the deal. MRjars are JARs! Which means that to execute tests, we need
|
||||||
|
// the JAR on classpath, not just classes + resources as Gradle usually does
|
||||||
|
testRuntimeClasspath.getAttributes()
|
||||||
|
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
|
||||||
|
|
||||||
|
TaskProvider<Test> testTask = tasks.register("java" + javaVersion + "Test", Test.class, test -> {
|
||||||
|
test.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||||
|
|
||||||
|
ConfigurableFileCollection testClassesDirs = objects.fileCollection();
|
||||||
|
testClassesDirs.from(testSourceSet.getOutput());
|
||||||
|
testClassesDirs.from(sharedTestSourceSet.getOutput());
|
||||||
|
test.setTestClassesDirs(testClassesDirs);
|
||||||
|
ConfigurableFileCollection classpath = objects.fileCollection();
|
||||||
|
// must put the MRJar first on classpath
|
||||||
|
classpath.from(tasks.named("jar"));
|
||||||
|
// then we put the specific test sourceset tests, so that we can override
|
||||||
|
// the shared versions
|
||||||
|
classpath.from(testSourceSet.getOutput());
|
||||||
|
|
||||||
|
// then we add the shared tests
|
||||||
|
classpath.from(sharedTestSourceSet.getRuntimeClasspath());
|
||||||
|
test.setClasspath(classpath);
|
||||||
|
});
|
||||||
|
return testTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureMultiReleaseJar(int version, SourceSet languageSourceSet) {
|
||||||
|
tasks.named("jar", Jar.class, jar -> {
|
||||||
|
jar.into("META-INF/versions/" + version, s -> s.from(languageSourceSet.getOutput()));
|
||||||
|
Attributes attributes = jar.getManifest().getAttributes();
|
||||||
|
attributes.put("Multi-Release", "true");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.build.multirelease;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||||
|
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||||
|
import org.gradle.api.model.ObjectFactory;
|
||||||
|
import org.gradle.api.plugins.ExtensionContainer;
|
||||||
|
import org.gradle.api.plugins.JavaPlugin;
|
||||||
|
import org.gradle.api.plugins.JavaPluginExtension;
|
||||||
|
import org.gradle.api.tasks.TaskContainer;
|
||||||
|
import org.gradle.jvm.toolchain.JavaToolchainService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plugin which adds support for building multi-release jars
|
||||||
|
* with Gradle.
|
||||||
|
* @author Cedric Champeau
|
||||||
|
* @author Brian Clozel
|
||||||
|
* @see <a href="https://github.com/melix/mrjar-gradle-plugin">original project</a>
|
||||||
|
*/
|
||||||
|
public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected JavaToolchainService getToolchains() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply(Project project) {
|
||||||
|
project.getPlugins().apply(JavaPlugin.class);
|
||||||
|
ExtensionContainer extensions = project.getExtensions();
|
||||||
|
JavaPluginExtension javaPluginExtension = extensions.getByType(JavaPluginExtension.class);
|
||||||
|
ConfigurationContainer configurations = project.getConfigurations();
|
||||||
|
TaskContainer tasks = project.getTasks();
|
||||||
|
DependencyHandler dependencies = project.getDependencies();
|
||||||
|
ObjectFactory objects = project.getObjects();
|
||||||
|
extensions.create("multiRelease", MultiReleaseExtension.class,
|
||||||
|
javaPluginExtension.getSourceSets(),
|
||||||
|
configurations,
|
||||||
|
tasks,
|
||||||
|
dependencies,
|
||||||
|
objects);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.build.multirelease;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import org.gradle.testkit.runner.BuildResult;
|
||||||
|
import org.gradle.testkit.runner.GradleRunner;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link MultiReleaseJarPlugin}
|
||||||
|
*/
|
||||||
|
public class MultiReleaseJarPluginTests {
|
||||||
|
|
||||||
|
private File projectDir;
|
||||||
|
|
||||||
|
private File buildFile;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup(@TempDir File projectDir) {
|
||||||
|
this.projectDir = projectDir;
|
||||||
|
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configureSourceSets() throws IOException {
|
||||||
|
writeBuildFile("""
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.build.multiReleaseJar'
|
||||||
|
}
|
||||||
|
multiRelease { releaseVersions 21, 24 }
|
||||||
|
task printSourceSets {
|
||||||
|
doLast {
|
||||||
|
sourceSets.all { println it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
BuildResult buildResult = runGradle("printSourceSets");
|
||||||
|
assertThat(buildResult.getOutput()).contains("main", "test", "java21", "java21Test", "java24", "java24Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configureToolchainReleaseVersion() throws IOException {
|
||||||
|
writeBuildFile("""
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.build.multiReleaseJar'
|
||||||
|
}
|
||||||
|
multiRelease { releaseVersions 21 }
|
||||||
|
task printReleaseVersion {
|
||||||
|
doLast {
|
||||||
|
tasks.all { println it.name }
|
||||||
|
tasks.named("compileJava21Java") {
|
||||||
|
println "compileJava21Java releaseVersion: ${it.options.release.get()}"
|
||||||
|
}
|
||||||
|
tasks.named("compileJava21TestJava") {
|
||||||
|
println "compileJava21TestJava releaseVersion: ${it.options.release.get()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
BuildResult buildResult = runGradle("printReleaseVersion");
|
||||||
|
assertThat(buildResult.getOutput()).contains("compileJava21Java releaseVersion: 21")
|
||||||
|
.contains("compileJava21TestJava releaseVersion: 21");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void packageInJar() throws IOException {
|
||||||
|
writeBuildFile("""
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.build.multiReleaseJar'
|
||||||
|
}
|
||||||
|
version = '1.2.3'
|
||||||
|
multiRelease { releaseVersions 17 }
|
||||||
|
""");
|
||||||
|
writeClass("src/main/java17", "Main.java", """
|
||||||
|
public class Main {}
|
||||||
|
""");
|
||||||
|
BuildResult buildResult = runGradle("assemble");
|
||||||
|
File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3.jar");
|
||||||
|
assertThat(file).exists();
|
||||||
|
try (JarFile jar = new JarFile(file)) {
|
||||||
|
Attributes mainAttributes = jar.getManifest().getMainAttributes();
|
||||||
|
assertThat(mainAttributes.getValue("Multi-Release")).isEqualTo("true");
|
||||||
|
|
||||||
|
assertThat(jar.entries().asIterator()).toIterable()
|
||||||
|
.anyMatch(entry -> entry.getName().equals("META-INF/versions/17/Main.class"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBuildFile(String buildContent) throws IOException {
|
||||||
|
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
|
||||||
|
out.print(buildContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||||
|
Path folder = this.projectDir.toPath().resolve(path);
|
||||||
|
Files.createDirectories(folder);
|
||||||
|
Path filePath = folder.resolve(fileName);
|
||||||
|
Files.createFile(filePath);
|
||||||
|
Files.writeString(filePath, fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildResult runGradle(String... args) {
|
||||||
|
return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,99 +0,0 @@
|
||||||
/**
|
|
||||||
* Apply the JVM Toolchain conventions
|
|
||||||
* See https://docs.gradle.org/current/userguide/toolchains.html
|
|
||||||
*
|
|
||||||
* One can choose the toolchain to use for compiling and running the TEST sources.
|
|
||||||
* These options apply to Java, Kotlin and Groovy test sources when available.
|
|
||||||
* {@code "./gradlew check -PtestToolchain=22"} will use a JDK22
|
|
||||||
* toolchain for compiling and running the test SourceSet.
|
|
||||||
*
|
|
||||||
* By default, the main build will fall back to using the a JDK 17
|
|
||||||
* toolchain (and 17 language level) for all main sourceSets.
|
|
||||||
* See {@link org.springframework.build.JavaConventions}.
|
|
||||||
*
|
|
||||||
* 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 JDK17
|
|
||||||
* /opt/openjdk/java17
|
|
||||||
* $ echo JDK22
|
|
||||||
* /opt/openjdk/java22
|
|
||||||
* $ ./gradlew -Porg.gradle.java.installations.fromEnv=JDK17,JDK22 check
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @author Brian Clozel
|
|
||||||
* @author Sam Brannen
|
|
||||||
*/
|
|
||||||
|
|
||||||
def testToolchainConfigured() {
|
|
||||||
return project.hasProperty('testToolchain') && project.testToolchain
|
|
||||||
}
|
|
||||||
|
|
||||||
def testToolchainLanguageVersion() {
|
|
||||||
if (testToolchainConfigured()) {
|
|
||||||
return JavaLanguageVersion.of(project.testToolchain.toString())
|
|
||||||
}
|
|
||||||
return JavaLanguageVersion.of(17)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.withType(JavaPlugin).configureEach {
|
|
||||||
// Configure a specific Java Toolchain for compiling and running tests if the 'testToolchain' property is defined
|
|
||||||
if (testToolchainConfigured()) {
|
|
||||||
def testLanguageVersion = testToolchainLanguageVersion()
|
|
||||||
tasks.withType(JavaCompile).matching { it.name.contains("Test") }.configureEach {
|
|
||||||
javaCompiler = javaToolchains.compilerFor {
|
|
||||||
languageVersion = testLanguageVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tasks.withType(Test).configureEach{
|
|
||||||
javaLauncher = javaToolchains.launcherFor {
|
|
||||||
languageVersion = testLanguageVersion
|
|
||||||
}
|
|
||||||
// Enable Java experimental support in Bytebuddy
|
|
||||||
// Bytebuddy 1.15.4 supports JDK <= 24
|
|
||||||
// see https://github.com/raphw/byte-buddy/blob/master/release-notes.md
|
|
||||||
if (testLanguageVersion.compareTo(JavaLanguageVersion.of(24)) > 0 ) {
|
|
||||||
jvmArgs("-Dnet.bytebuddy.experimental=true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the JMH plugin to use the toolchain for generating and running JMH bytecode
|
|
||||||
pluginManager.withPlugin("me.champeau.jmh") {
|
|
||||||
if (testToolchainConfigured()) {
|
|
||||||
tasks.matching { it.name.contains('jmh') && it.hasProperty('javaLauncher') }.configureEach {
|
|
||||||
javaLauncher.set(javaToolchains.launcherFor {
|
|
||||||
languageVersion.set(testToolchainLanguageVersion())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
tasks.withType(JavaCompile).matching { it.name.contains("Jmh") }.configureEach {
|
|
||||||
javaCompiler = javaToolchains.compilerFor {
|
|
||||||
languageVersion = testToolchainLanguageVersion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store resolved Toolchain JVM information as custom values in the build scan.
|
|
||||||
rootProject.ext {
|
|
||||||
resolvedMainToolchain = false
|
|
||||||
resolvedTestToolchain = false
|
|
||||||
}
|
|
||||||
gradle.taskGraph.afterTask { Task task, TaskState state ->
|
|
||||||
if (!resolvedMainToolchain && task instanceof JavaCompile && task.javaCompiler.isPresent()) {
|
|
||||||
def metadata = task.javaCompiler.get().metadata
|
|
||||||
task.project.develocity.buildScan.value('Main toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
|
|
||||||
resolvedMainToolchain = true
|
|
||||||
}
|
|
||||||
if (testToolchainConfigured() && !resolvedTestToolchain && task instanceof Test && task.javaLauncher.isPresent()) {
|
|
||||||
def metadata = task.javaLauncher.get().metadata
|
|
||||||
task.project.develocity.buildScan.value('Test toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
|
|
||||||
resolvedTestToolchain = true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
import org.springframework.build.shadow.ShadowSource
|
import org.springframework.build.shadow.ShadowSource
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'me.champeau.mrjar'
|
id 'org.springframework.build.multiReleaseJar'
|
||||||
}
|
}
|
||||||
|
|
||||||
description = "Spring Core"
|
description = "Spring Core"
|
||||||
|
@ -11,7 +11,7 @@ apply plugin: "kotlin"
|
||||||
apply plugin: "kotlinx-serialization"
|
apply plugin: "kotlinx-serialization"
|
||||||
|
|
||||||
multiRelease {
|
multiRelease {
|
||||||
targetVersions 17, 21
|
releaseVersions 21
|
||||||
}
|
}
|
||||||
|
|
||||||
def javapoetVersion = "1.13.0"
|
def javapoetVersion = "1.13.0"
|
||||||
|
|
Loading…
Reference in New Issue