diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index b47346aff3..dd6c18f6b5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -88,8 +88,8 @@ final class JavaPluginAction implements PluginApplicationAction { "Assembles an executable jar archive containing the main classes and their dependencies."); bootJar.setGroup(BasePlugin.BUILD_GROUP); bootJar.classpath((Callable) () -> { - JavaPluginConvention convention = project.getConvention().getPlugin(JavaPluginConvention.class); - SourceSet mainSourceSet = convention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); return mainSourceSet.getRuntimeClasspath(); }); bootJar.conventionMapping("mainClassName", new MainClassConvention(project, bootJar::getClasspath)); @@ -102,6 +102,7 @@ final class JavaPluginAction implements PluginApplicationAction { buildImage.setDescription("Builds an OCI image of the application using the output of the bootJar task"); buildImage.setGroup(BasePlugin.BUILD_GROUP); buildImage.getJar().set(bootJar.getArchiveFile()); + buildImage.getTargetJavaVersion().set(javaPluginConvention(project).getTargetCompatibility()); } private void configureArtifactPublication(BootJar bootJar) { @@ -110,11 +111,11 @@ final class JavaPluginAction implements PluginApplicationAction { } private void configureBootRunTask(Project project) { - JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); BootRun run = project.getTasks().create("bootRun", BootRun.class); run.setDescription("Runs this project as a Spring Boot application."); run.setGroup(ApplicationPlugin.APPLICATION_GROUP); - run.classpath(javaConvention.getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()); + run.classpath(javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME) + .getRuntimeClasspath()); run.getConventionMapping().map("jvmArgs", () -> { if (project.hasProperty("applicationDefaultJvmArgs")) { return project.property("applicationDefaultJvmArgs"); @@ -124,6 +125,10 @@ final class JavaPluginAction implements PluginApplicationAction { run.conventionMapping("main", new MainClassConvention(project, run::getClasspath)); } + private JavaPluginConvention javaPluginConvention(Project project) { + return project.getConvention().getPlugin(JavaPluginConvention.class); + } + private void configureUtf8Encoding(Project project) { project.afterEvaluate((evaluated) -> evaluated.getTasks().withType(JavaCompile.class, (compile) -> { if (compile.getOptions().getEncoding() == null) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index adbb22a79e..bc96cf8156 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -21,9 +21,11 @@ import java.util.HashMap; import java.util.Map; import org.gradle.api.DefaultTask; +import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; @@ -48,8 +50,12 @@ import org.springframework.util.StringUtils; */ public class BootBuildImage extends DefaultTask { + private static final String OPENJDK_BUILDPACK_JAVA_VERSION_KEY = "BP_JAVA_VERSION"; + private RegularFileProperty jar; + private Property targetJavaVersion; + private String imageName; private String builder; @@ -62,6 +68,7 @@ public class BootBuildImage extends DefaultTask { public BootBuildImage() { this.jar = getProject().getObjects().fileProperty(); + this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class); } /** @@ -73,6 +80,17 @@ public class BootBuildImage extends DefaultTask { return this.jar; } + /** + * Returns the target Java version of the project (e.g. as provided by the + * {@code targetCompatibility} build property). + * @return the target Java version + */ + @Input + @Optional + public Property getTargetJavaVersion() { + return this.targetJavaVersion; + } + /** * Returns the name of the image that will be built. When {@code null}, the name will * be derived from the {@link Project Project's} {@link Project#getName() name} and @@ -189,9 +207,8 @@ public class BootBuildImage extends DefaultTask { } BuildRequest createRequest() { - BuildRequest request = customize(BuildRequest.of(determineImageReference(), + return customize(BuildRequest.of(determineImageReference(), (owner) -> new ZipFileTarArchive(this.jar.get().getAsFile(), owner))); - return request; } private ImageReference determineImageReference() { @@ -207,19 +224,41 @@ public class BootBuildImage extends DefaultTask { } private BuildRequest customize(BuildRequest request) { - if (StringUtils.hasText(this.builder)) { - request = request.withBuilder(ImageReference.of(this.builder)); - } - if (this.environment != null && !this.environment.isEmpty()) { - request = request.withEnv(this.environment); - } - String springBootVersion = VersionExtractor.forClass(BootBuildImage.class); - if (StringUtils.hasText(springBootVersion)) { - request = request.withCreator(Creator.withVersion(springBootVersion)); - } + request = customizeBuilder(request); + request = customizeEnvironment(request); + request = customizeCreator(request); request = request.withCleanCache(this.cleanCache); request = request.withVerboseLogging(this.verboseLogging); return request; } + private BuildRequest customizeBuilder(BuildRequest request) { + if (StringUtils.hasText(this.builder)) { + return request.withBuilder(ImageReference.of(this.builder)); + } + return request; + } + + private BuildRequest customizeEnvironment(BuildRequest request) { + if (this.environment != null && !this.environment.isEmpty()) { + request = request.withEnv(this.environment); + } + if (this.targetJavaVersion.isPresent() && !request.getEnv().containsKey(OPENJDK_BUILDPACK_JAVA_VERSION_KEY)) { + request = request.withEnv(OPENJDK_BUILDPACK_JAVA_VERSION_KEY, translateTargetJavaVersion()); + } + return request; + } + + private BuildRequest customizeCreator(BuildRequest request) { + String springBootVersion = VersionExtractor.forClass(BootBuildImage.class); + if (StringUtils.hasText(springBootVersion)) { + return request.withCreator(Creator.withVersion(springBootVersion)); + } + return request; + } + + private String translateTargetJavaVersion() { + return this.targetJavaVersion.get().getMajorVersion() + ".*"; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java index 6f08ab5f5e..f7d528ba42 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.HashMap; import java.util.Map; +import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; @@ -128,6 +129,27 @@ class BootBuildImageTests { .hasSize(2); } + @Test + void whenJavaVersionIsSetInEnvironmentItIsIncludedInTheRequest() { + this.buildImage.environment("BP_JAVA_VERSION", "from-env"); + this.buildImage.getTargetJavaVersion().set(JavaVersion.VERSION_1_8); + assertThat(this.buildImage.createRequest().getEnv()).containsEntry("BP_JAVA_VERSION", "from-env").hasSize(1); + } + + @Test + void whenTargetCompatibilityIsSetThenJavaVersionIsIncludedInTheRequest() { + this.buildImage.getTargetJavaVersion().set(JavaVersion.VERSION_1_8); + assertThat(this.buildImage.createRequest().getEnv()).containsEntry("BP_JAVA_VERSION", "8.*").hasSize(1); + } + + @Test + void whenTargetCompatibilityIsSetThenJavaVersionIsAddedToEnvironment() { + this.buildImage.environment("ALPHA", "a"); + this.buildImage.getTargetJavaVersion().set(JavaVersion.VERSION_11); + assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") + .containsEntry("BP_JAVA_VERSION", "11.*").hasSize(2); + } + @Test void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() { assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index de29bc5949..fc8caa9b81 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -63,6 +63,8 @@ import org.springframework.util.StringUtils; @Execute(phase = LifecyclePhase.PACKAGE) public class BuildImageMojo extends AbstractPackagerMojo { + private static final String OPENJDK_BUILDPACK_JAVA_VERSION_KEY = "BP_JAVA_VERSION"; + /** * Directory containing the JAR. * @since 2.3.0 @@ -137,7 +139,7 @@ public class BuildImageMojo extends AbstractPackagerMojo { private File getJarFile() { // We can use 'project.getArtifact().getFile()' because that was done in a - // forked lifecyle and is now null + // forked lifecycle and is now null StringBuilder name = new StringBuilder(this.finalName); if (StringUtils.hasText(this.classifier)) { name.append("-").append(this.classifier); @@ -147,6 +149,23 @@ public class BuildImageMojo extends AbstractPackagerMojo { } private BuildRequest customize(BuildRequest request) { + request = customizeEnvironment(request); + request = customizeCreator(request); + return request; + } + + private BuildRequest customizeEnvironment(BuildRequest request) { + if (!request.getEnv().containsKey(OPENJDK_BUILDPACK_JAVA_VERSION_KEY)) { + JavaCompilerPluginConfiguration compilerConfiguration = new JavaCompilerPluginConfiguration(this.project); + String targetJavaVersion = compilerConfiguration.getTargetMajorVersion(); + if (StringUtils.hasText(targetJavaVersion)) { + return request.withEnv(OPENJDK_BUILDPACK_JAVA_VERSION_KEY, targetJavaVersion + ".*"); + } + } + return request; + } + + private BuildRequest customizeCreator(BuildRequest request) { String springBootVersion = VersionExtractor.forClass(BuildImageMojo.class); if (StringUtils.hasText(springBootVersion)) { request = request.withCreator(Creator.withVersion(springBootVersion)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/JavaCompilerPluginConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/JavaCompilerPluginConfiguration.java new file mode 100644 index 0000000000..b44be9a06d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/JavaCompilerPluginConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2020 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.maven; + +import java.util.Arrays; + +import org.apache.maven.model.Plugin; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.xml.Xpp3Dom; + +/** + * Provides access to the Maven Java Compiler plugin configuration. + * + * @author Scott Frederick + */ +class JavaCompilerPluginConfiguration { + + private final MavenProject project; + + JavaCompilerPluginConfiguration(MavenProject project) { + this.project = project; + } + + String getSourceMajorVersion() { + String version = getConfigurationValue("source"); + + if (version == null) { + version = getPropertyValue("maven.compiler.source"); + } + + return majorVersionFor(version); + } + + String getTargetMajorVersion() { + String version = getConfigurationValue("target"); + + if (version == null) { + version = getPropertyValue("maven.compiler.target"); + } + + return majorVersionFor(version); + } + + private String getConfigurationValue(String propertyName) { + Plugin plugin = this.project.getPlugin("org.apache.maven.plugins:maven-compiler-plugin"); + if (plugin != null) { + Object pluginConfiguration = plugin.getConfiguration(); + if (pluginConfiguration instanceof Xpp3Dom) { + Xpp3Dom dom = (Xpp3Dom) pluginConfiguration; + return getNodeValue(dom, propertyName); + } + } + return null; + } + + private String getPropertyValue(String propertyName) { + if (this.project.getProperties().containsKey(propertyName)) { + return this.project.getProperties().get(propertyName).toString(); + } + return null; + } + + private String getNodeValue(Xpp3Dom dom, String... childNames) { + Xpp3Dom childNode = dom.getChild(childNames[0]); + + if (childNode == null) { + return null; + } + + if (childNames.length > 1) { + return getNodeValue(childNode, Arrays.copyOfRange(childNames, 1, childNames.length)); + } + + return childNode.getValue(); + } + + private String majorVersionFor(String version) { + if (version != null && version.startsWith("1.")) { + return version.substring("1.".length()); + } + return version; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/JavaCompilerPluginConfigurationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/JavaCompilerPluginConfigurationTests.java new file mode 100644 index 0000000000..0bcd407642 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/JavaCompilerPluginConfigurationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2020 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.maven; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Arrays; +import java.util.Properties; + +import org.apache.maven.model.Plugin; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JavaCompilerPluginConfiguration}. + * + * @author Scott Frederick + */ +class JavaCompilerPluginConfigurationTests { + + private MavenProject project; + + private Plugin plugin; + + @BeforeEach + void setUp() { + this.project = mock(MavenProject.class); + this.plugin = mock(Plugin.class); + given(this.project.getPlugin(anyString())).willReturn(this.plugin); + } + + @Test + void versionsAreNullWithNoConfiguration() { + given(this.plugin.getConfiguration()).willReturn(null); + given(this.project.getProperties()).willReturn(new Properties()); + JavaCompilerPluginConfiguration configuration = new JavaCompilerPluginConfiguration(this.project); + assertThat(configuration.getSourceMajorVersion()).isNull(); + assertThat(configuration.getTargetMajorVersion()).isNull(); + } + + @Test + void versionsAreReturnedFromConfiguration() throws IOException, XmlPullParserException { + Xpp3Dom dom = buildConfigurationDom("1.9", "11"); + given(this.plugin.getConfiguration()).willReturn(dom); + Properties properties = new Properties(); + properties.setProperty("maven.compiler.source", "1.8"); + properties.setProperty("maven.compiler.target", "10"); + given(this.project.getProperties()).willReturn(properties); + JavaCompilerPluginConfiguration configuration = new JavaCompilerPluginConfiguration(this.project); + assertThat(configuration.getSourceMajorVersion()).isEqualTo("9"); + assertThat(configuration.getTargetMajorVersion()).isEqualTo("11"); + } + + @Test + void versionsAreReturnedFromProperties() { + given(this.plugin.getConfiguration()).willReturn(null); + Properties properties = new Properties(); + properties.setProperty("maven.compiler.source", "1.8"); + properties.setProperty("maven.compiler.target", "11"); + given(this.project.getProperties()).willReturn(properties); + JavaCompilerPluginConfiguration configuration = new JavaCompilerPluginConfiguration(this.project); + assertThat(configuration.getSourceMajorVersion()).isEqualTo("8"); + assertThat(configuration.getTargetMajorVersion()).isEqualTo("11"); + } + + private Xpp3Dom buildConfigurationDom(String... properties) throws IOException, XmlPullParserException { + return Xpp3DomBuilder + .build(new StringReader("" + Arrays.toString(properties) + "")); + } + +}