From a749b2fd5818e521b71fd684732f435d74c3f021 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 6 Jan 2026 12:25:53 +0000 Subject: [PATCH] Allow projects to configure their system requirements Closes gh-48684 --- .../boot/build/ConventionsPlugin.java | 4 +- .../boot/build/JavaConventions.java | 18 ++++-- .../build/SystemRequirementsExtension.java | 58 +++++++++++++++++++ .../build/toolchain/ToolchainExtension.java | 23 ++++---- .../boot/build/toolchain/ToolchainPlugin.java | 10 +++- 5 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java index 7c7a47e5051..d0c9cccef88 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java @@ -43,8 +43,10 @@ public class ConventionsPlugin implements Plugin { @Override public void apply(Project project) { + SystemRequirementsExtension systemRequirements = project.getExtensions() + .create("systemRequirements", SystemRequirementsExtension.class); new NoHttpConventions().apply(project); - new JavaConventions().apply(project); + new JavaConventions(systemRequirements.getJava()).apply(project); new MavenPublishingConventions().apply(project); new AntoraConventions().apply(project); new KotlinConventions().apply(project); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java index 2b1f053a67d..5c3a784c0b4 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java @@ -32,7 +32,6 @@ import io.spring.gradle.nullability.NullabilityPluginExtension; import io.spring.javaformat.gradle.SpringJavaFormatPlugin; import io.spring.javaformat.gradle.tasks.CheckFormat; import io.spring.javaformat.gradle.tasks.Format; -import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; @@ -53,7 +52,9 @@ import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.testing.Test; import org.gradle.external.javadoc.CoreJavadocOptions; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.springframework.boot.build.SystemRequirementsExtension.JavaSpec; import org.springframework.boot.build.architecture.ArchitecturePlugin; import org.springframework.boot.build.classpath.CheckClasspathForProhibitedDependencies; import org.springframework.boot.build.optional.OptionalDependenciesPlugin; @@ -127,7 +128,11 @@ import org.springframework.util.StringUtils; */ class JavaConventions { - private static final String SOURCE_AND_TARGET_COMPATIBILITY = "17"; + private final JavaSpec javaSpec; + + JavaConventions(JavaSpec javaSpec) { + this.javaSpec = javaSpec; + } void apply(Project project) { project.getPlugins().withType(JavaBasePlugin.class, (java) -> { @@ -165,7 +170,7 @@ class JavaConventions { jar.manifest((manifest) -> { Map attributes = new TreeMap<>(); attributes.put("Automatic-Module-Name", project.getName().replace("-", ".")); - attributes.put("Build-Jdk-Spec", SOURCE_AND_TARGET_COMPATIBILITY); + attributes.put("Build-Jdk-Spec", this.javaSpec.getVersion().get().toString()); attributes.put("Built-By", "Spring"); attributes.put("Implementation-Title", determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar)); @@ -243,14 +248,15 @@ class JavaConventions { } private void configureJavaConventions(Project project) { + JavaLanguageVersion javaVersion = this.javaSpec.getVersion().get(); if (!project.hasProperty("toolchainVersion")) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - javaPluginExtension.setSourceCompatibility(JavaVersion.toVersion(SOURCE_AND_TARGET_COMPATIBILITY)); - javaPluginExtension.setTargetCompatibility(JavaVersion.toVersion(SOURCE_AND_TARGET_COMPATIBILITY)); + javaPluginExtension.setSourceCompatibility(javaVersion); + javaPluginExtension.setTargetCompatibility(javaVersion); } project.getTasks().withType(JavaCompile.class, (compile) -> { compile.getOptions().setEncoding("UTF-8"); - compile.getOptions().getRelease().set(17); + compile.getOptions().getRelease().set(javaVersion.asInt()); List args = compile.getOptions().getCompilerArgs(); if (!args.contains("-parameters")) { args.add("-parameters"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java new file mode 100644 index 00000000000..cecf4003624 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java @@ -0,0 +1,58 @@ +/* + * Copyright 2026 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.boot.build; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Property; +import org.gradle.jvm.toolchain.JavaLanguageVersion; + +/** + * DSL extension for configuring a project's system requirements. + * + * @author Andy Wilkinson + */ +public class SystemRequirementsExtension { + + private final JavaSpec javaSpec; + + @Inject + public SystemRequirementsExtension(ObjectFactory objects) { + this.javaSpec = objects.newInstance(JavaSpec.class); + } + + public void java(Action action) { + action.execute(this.javaSpec); + } + + public JavaSpec getJava() { + return this.javaSpec; + } + + public abstract static class JavaSpec { + + public JavaSpec() { + getVersion().convention(JavaLanguageVersion.of(17)); + } + + public abstract Property getVersion(); + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainExtension.java index 6ce52decf6e..82449aa39cc 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainExtension.java @@ -21,33 +21,30 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.springframework.boot.build.SystemRequirementsExtension; +import org.springframework.boot.build.SystemRequirementsExtension.JavaSpec; + /** * DSL extension for {@link ToolchainPlugin}. * * @author Christoph Dreis */ -public class ToolchainExtension { - - private final Property maximumCompatibleJavaVersion; - - private final ListProperty testJvmArgs; +public abstract class ToolchainExtension { private final JavaLanguageVersion javaVersion; public ToolchainExtension(Project project) { - this.maximumCompatibleJavaVersion = project.getObjects().property(JavaLanguageVersion.class); - this.testJvmArgs = project.getObjects().listProperty(String.class); String toolchainVersion = (String) project.findProperty("toolchainVersion"); this.javaVersion = (toolchainVersion != null) ? JavaLanguageVersion.of(toolchainVersion) : null; + JavaSpec javaSpec = project.getExtensions().getByType(SystemRequirementsExtension.class).getJava(); + getMinimumCompatibleJavaVersion().convention(javaSpec.getVersion()); } - public Property getMaximumCompatibleJavaVersion() { - return this.maximumCompatibleJavaVersion; - } + public abstract Property getMinimumCompatibleJavaVersion(); - public ListProperty getTestJvmArgs() { - return this.testJvmArgs; - } + public abstract Property getMaximumCompatibleJavaVersion(); + + public abstract ListProperty getTestJvmArgs(); JavaLanguageVersion getJavaVersion() { return this.javaVersion; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java index 6b37f10b6ef..14c95b57d5b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/toolchain/ToolchainPlugin.java @@ -53,9 +53,13 @@ public class ToolchainPlugin implements Plugin { } private boolean isJavaVersionSupported(ToolchainExtension toolchain, JavaLanguageVersion toolchainVersion) { - return toolchain.getMaximumCompatibleJavaVersion() - .map((version) -> version.canCompileOrRun(toolchainVersion)) - .getOrElse(true); + JavaLanguageVersion minimumVersion = toolchain.getMinimumCompatibleJavaVersion().getOrNull(); + if (minimumVersion == null || toolchainVersion.canCompileOrRun(minimumVersion)) { + return toolchain.getMaximumCompatibleJavaVersion() + .map((version) -> version.canCompileOrRun(toolchainVersion)) + .getOrElse(true); + } + return false; } private void disableToolchainTasks(Project project) {