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 d0c9cccef88..f1d7239bf14 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java @@ -46,7 +46,7 @@ public class ConventionsPlugin implements Plugin { SystemRequirementsExtension systemRequirements = project.getExtensions() .create("systemRequirements", SystemRequirementsExtension.class); new NoHttpConventions().apply(project); - new JavaConventions(systemRequirements.getJava()).apply(project); + new JavaConventions(systemRequirements).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/EclipseConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java index f692b3e70f0..3116cfc7b95 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/EclipseConventions.java @@ -16,13 +16,17 @@ package org.springframework.boot.build; +import org.gradle.api.DomainObjectCollection; +import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.tasks.TaskProvider; import org.gradle.plugins.ide.api.XmlFileContentMerger; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.Classpath; import org.gradle.plugins.ide.eclipse.model.ClasspathEntry; import org.gradle.plugins.ide.eclipse.model.EclipseClasspath; +import org.gradle.plugins.ide.eclipse.model.EclipseJdt; import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.plugins.ide.eclipse.model.Library; @@ -35,12 +39,35 @@ import org.gradle.plugins.ide.eclipse.model.Library; class EclipseConventions { void apply(Project project) { - project.getPlugins() - .withType(EclipsePlugin.class, - (eclipse) -> project.getPlugins().withType(JavaBasePlugin.class, (javaBase) -> { - EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class); - eclipseModel.classpath(this::configureClasspath); - })); + project.getPlugins().withType(EclipsePlugin.class, (eclipse) -> configure(project, eclipse)); + } + + private DomainObjectCollection configure(Project project, EclipsePlugin eclipsePlugin) { + TaskProvider eclipseSynchronizeJdtSettings = registerEclipseSynchronizeJdtSettingsTask( + project); + return project.getPlugins().withType(JavaBasePlugin.class, (javaBase) -> { + EclipseModel model = project.getExtensions().getByType(EclipseModel.class); + model.synchronizationTasks(eclipseSynchronizeJdtSettings); + model.jdt(this::configureJdt); + model.classpath(this::configureClasspath); + }); + } + + private TaskProvider registerEclipseSynchronizeJdtSettingsTask(Project project) { + TaskProvider taskProvider = project.getTasks() + .register("eclipseSynchronizateJdt", EclipseSynchronizeJdtSettings.class); + taskProvider.configure((task) -> { + task.setDescription("Synchronizate the Eclipse JDT settings file from Buildship."); + task.setOutputFile(project.file(".settings/org.eclipse.jdt.core.prefs")); + task.setInputFile(project.file(".settings/org.eclipse.jdt.core.prefs")); + }); + return taskProvider; + } + + private void configureJdt(EclipseJdt jdt) { + jdt.setSourceCompatibility(JavaVersion.toVersion(JavaConventions.RUNTIME_JAVA_VERSION)); + jdt.setTargetCompatibility(JavaVersion.toVersion(JavaConventions.RUNTIME_JAVA_VERSION)); + jdt.setJavaRuntimeName("JavaSE-" + JavaConventions.BUILD_JAVA_VERSION); } private void configureClasspath(EclipseClasspath classpath) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/EclipseSynchronizeJdtSettings.java b/buildSrc/src/main/java/org/springframework/boot/build/EclipseSynchronizeJdtSettings.java new file mode 100644 index 00000000000..e4a477b1139 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/EclipseSynchronizeJdtSettings.java @@ -0,0 +1,66 @@ +/* + * Copyright 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.boot.build; + +import java.util.Properties; + +import org.gradle.api.Task; +import org.gradle.api.internal.PropertiesTransformer; +import org.gradle.plugins.ide.api.PropertiesGeneratorTask; +import org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject; + +import org.springframework.boot.build.EclipseSynchronizeJdtSettings.Configuration; + +/** + * {@link Task} to do something. + * + * @author Phillip Webb + */ +public abstract class EclipseSynchronizeJdtSettings extends PropertiesGeneratorTask { + + @Override + protected Configuration create() { + return new Configuration(getTransformer()); + } + + @Override + protected void configure(Configuration configuration) { + } + + static class Configuration extends PropertiesPersistableConfigurationObject { + + Configuration(PropertiesTransformer transformer) { + super(transformer); + } + + @Override + protected String getDefaultResourceName() { + return null; + } + + @Override + protected void load(Properties properties) { + } + + @Override + protected void store(Properties properties) { + properties.put("org.eclipse.jdt.core.compiler.release", "true"); + } + + } + +} 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 06fff0e9be3..5d0db22357f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java @@ -16,8 +16,10 @@ package org.springframework.boot.build; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -32,6 +34,8 @@ 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.GradleException; +import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; @@ -52,8 +56,10 @@ 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.JavaCompiler; +import org.gradle.jvm.toolchain.JavaInstallationMetadata; +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,10 +133,14 @@ import org.springframework.util.StringUtils; */ class JavaConventions { - private final JavaSpec javaSpec; + public static final int BUILD_JAVA_VERSION = 25; - JavaConventions(JavaSpec javaSpec) { - this.javaSpec = javaSpec; + public static final int RUNTIME_JAVA_VERSION = 17; + + private final SystemRequirementsExtension systemRequirements; + + JavaConventions(SystemRequirementsExtension systemRequirements) { + this.systemRequirements = systemRequirements; } void apply(Project project) { @@ -169,7 +179,8 @@ class JavaConventions { jar.manifest((manifest) -> { Map attributes = new TreeMap<>(); attributes.put("Automatic-Module-Name", project.getName().replace("-", ".")); - attributes.put("Build-Jdk-Spec", this.javaSpec.getVersion()); + // Build-Jdk-Spec is used by buildpacks to pick the JRE to install + attributes.put("Build-Jdk-Spec", this.systemRequirements.getJava().getVersion()); attributes.put("Built-By", "Spring"); attributes.put("Implementation-Title", determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar)); @@ -248,17 +259,30 @@ class JavaConventions { private void configureJavaConventions(Project project) { project.getTasks().withType(JavaCompile.class, (compile) -> { + compile.doFirst((task) -> assertCompatible(compile)); compile.getOptions().setEncoding("UTF-8"); - compile.getOptions().getRelease().set(this.javaSpec.getVersion()); - List args = compile.getOptions().getCompilerArgs(); - if (!args.contains("-parameters")) { - args.add("-parameters"); - } - args.addAll(Arrays.asList("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", + compile.getOptions().getRelease().set(RUNTIME_JAVA_VERSION); + Set args = new LinkedHashSet<>(compile.getOptions().getCompilerArgs()); + args.addAll(List.of("-parameters", "-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", "-Xlint:varargs")); + compile.getOptions().setCompilerArgs(new ArrayList<>(args)); }); } + private void assertCompatible(JavaCompile compile) { + JavaVersion requiredVersion = JavaVersion.toVersion(BUILD_JAVA_VERSION); + JavaVersion actualVersion = compile.getJavaCompiler() + .map(JavaCompiler::getMetadata) + .map(JavaInstallationMetadata::getLanguageVersion) + .map(JavaLanguageVersion::asInt) + .map(JavaVersion::toVersion) + .orElse(JavaVersion.current()) + .get(); + if (!actualVersion.isCompatibleWith(requiredVersion)) { + throw new GradleException("This project should be built with Java %s or above".formatted(requiredVersion)); + } + } + private void configureSpringJavaFormat(Project project) { project.getPlugins().apply(SpringJavaFormatPlugin.class); project.getTasks().withType(Format.class, (Format) -> Format.setEncoding("UTF-8")); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java index 697ff12dc68..2fb63d7af1b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/SystemRequirementsExtension.java @@ -19,10 +19,7 @@ package org.springframework.boot.build; import javax.inject.Inject; import org.gradle.api.Action; -import org.gradle.api.Project; import org.gradle.api.model.ObjectFactory; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.jvm.toolchain.JavaLanguageVersion; /** * DSL extension for configuring a project's system requirements. @@ -34,8 +31,8 @@ public class SystemRequirementsExtension { private final JavaSpec javaSpec; @Inject - public SystemRequirementsExtension(Project project, ObjectFactory objects) { - this.javaSpec = objects.newInstance(JavaSpec.class, project); + public SystemRequirementsExtension(ObjectFactory objects) { + this.javaSpec = objects.newInstance(JavaSpec.class); } public void java(Action action) { @@ -48,24 +45,13 @@ public class SystemRequirementsExtension { public abstract static class JavaSpec { - private final Project project; - private int version = 17; - @Inject - public JavaSpec(Project project) { - this.project = project; - } - public int getVersion() { return this.version; } public void setVersion(int version) { - JavaLanguageVersion javaVersion = JavaLanguageVersion.of(version); - JavaPluginExtension javaPluginExtension = this.project.getExtensions().getByType(JavaPluginExtension.class); - javaPluginExtension.setSourceCompatibility(javaVersion); - javaPluginExtension.setTargetCompatibility(javaVersion); this.version = version; } 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 a694dd005dc..a170a038c71 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 @@ -22,7 +22,6 @@ 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}. @@ -36,9 +35,10 @@ public abstract class ToolchainExtension { public ToolchainExtension(Project project) { String toolchainVersion = (String) project.findProperty("toolchainVersion"); this.javaVersion = (toolchainVersion != null) ? JavaLanguageVersion.of(toolchainVersion) : null; - JavaSpec javaSpec = project.getExtensions().getByType(SystemRequirementsExtension.class).getJava(); + SystemRequirementsExtension systemRequirements = project.getExtensions() + .getByType(SystemRequirementsExtension.class); getMinimumCompatibleJavaVersion() - .convention(project.provider(() -> JavaLanguageVersion.of(javaSpec.getVersion()))); + .convention(project.provider(() -> JavaLanguageVersion.of(systemRequirements.getJava().getVersion()))); } public abstract Property getMinimumCompatibleJavaVersion();