Add Build-Jdk-Spec to jar and war manifest when building with Gradle

This commit adds a `Build-Jdk-Spec` attribute to the manifest in a
jar or war file built with the Spring Boot Gradle plugin. This
aligns the Gradle plugin's behavior with the default Maven plugin
behavior.

This removes the need to set a `BP_JVM_VERSION` environment variable
when invoking Cloud Native Buildpacks, as the Paketo buildpacks will
honor `Build-Jdk-Spec` in a jar or war manifest to determine the
default JVM version.

Fixes gh-32829
pull/32880/head
Scott Frederick 2 years ago
parent 383d6c897f
commit c22e76632c

@ -150,6 +150,8 @@ final class JavaPluginAction implements PluginApplicationAction {
.provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class")); .provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class"));
bootJar.getMainClass().convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() bootJar.getMainClass().convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent()
? manifestStartClass : resolveMainClassName.get().readMainClassName())); ? manifestStartClass : resolveMainClassName.get().readMainClassName()));
bootJar.getTargetJavaVersion()
.set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility()));
}); });
} }
@ -158,8 +160,6 @@ final class JavaPluginAction implements PluginApplicationAction {
buildImage.setDescription("Builds an OCI image of the application using the output of the bootJar task"); buildImage.setDescription("Builds an OCI image of the application using the output of the bootJar task");
buildImage.setGroup(BasePlugin.BUILD_GROUP); buildImage.setGroup(BasePlugin.BUILD_GROUP);
buildImage.getArchiveFile().set(bootJar.get().getArchiveFile()); buildImage.getArchiveFile().set(bootJar.get().getArchiveFile());
buildImage.getTargetJavaVersion()
.set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility()));
}); });
} }

@ -25,6 +25,7 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.WarPlugin; import org.gradle.api.plugins.WarPlugin;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
@ -90,6 +91,8 @@ class WarPluginAction implements PluginApplicationAction {
bootWar.getMainClass() bootWar.getMainClass()
.convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() .convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent()
? manifestStartClass : resolveMainClassName.get().readMainClassName())); ? manifestStartClass : resolveMainClassName.get().readMainClassName()));
bootWar.getTargetJavaVersion()
.set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility()));
}); });
bootWarProvider.map(War::getClasspath); bootWarProvider.map(War::getClasspath);
return bootWarProvider; return bootWarProvider;
@ -109,4 +112,8 @@ class WarPluginAction implements PluginApplicationAction {
this.singlePublishedArtifact.addWarCandidate(bootWar); this.singlePublishedArtifact.addWarCandidate(bootWar);
} }
private JavaPluginExtension javaPluginExtension(Project project) {
return project.getExtensions().getByType(JavaPluginExtension.class);
}
} }

@ -17,6 +17,7 @@
package org.springframework.boot.gradle.tasks.bundling; package org.springframework.boot.gradle.tasks.bundling;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.Task; import org.gradle.api.Task;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
@ -110,4 +111,13 @@ public interface BootArchive extends Task {
*/ */
void setClasspath(FileCollection classpath); void setClasspath(FileCollection classpath);
/**
* 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
Property<JavaVersion> getTargetJavaVersion();
} }

@ -85,7 +85,7 @@ class BootArchiveSupport {
} }
void configureManifest(Manifest manifest, String mainClass, String classes, String lib, String classPathIndex, void configureManifest(Manifest manifest, String mainClass, String classes, String lib, String classPathIndex,
String layersIndex) { String layersIndex, String jdkVersion) {
Attributes attributes = manifest.getAttributes(); Attributes attributes = manifest.getAttributes();
attributes.putIfAbsent("Main-Class", this.loaderMainClass); attributes.putIfAbsent("Main-Class", this.loaderMainClass);
attributes.putIfAbsent("Start-Class", mainClass); attributes.putIfAbsent("Start-Class", mainClass);
@ -98,6 +98,7 @@ class BootArchiveSupport {
if (layersIndex != null) { if (layersIndex != null) {
attributes.putIfAbsent("Spring-Boot-Layers-Index", layersIndex); attributes.putIfAbsent("Spring-Boot-Layers-Index", layersIndex);
} }
attributes.putIfAbsent("Build-Jdk-Spec", jdkVersion);
} }
private String determineSpringBootVersion() { private String determineSpringBootVersion() {

@ -22,7 +22,6 @@ import java.util.Map;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.Task; import org.gradle.api.Task;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
@ -66,8 +65,6 @@ import org.springframework.util.StringUtils;
@DisableCachingByDefault @DisableCachingByDefault
public abstract class BootBuildImage extends DefaultTask { public abstract class BootBuildImage extends DefaultTask {
private static final String BUILDPACK_JVM_VERSION_KEY = "BP_JVM_VERSION";
private final Property<PullPolicy> pullPolicy; private final Property<PullPolicy> pullPolicy;
private final String projectName; private final String projectName;
@ -107,15 +104,6 @@ public abstract class BootBuildImage extends DefaultTask {
@PathSensitive(PathSensitivity.RELATIVE) @PathSensitive(PathSensitivity.RELATIVE)
public abstract RegularFileProperty getArchiveFile(); public abstract RegularFileProperty getArchiveFile();
/**
* 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 abstract Property<JavaVersion> getTargetJavaVersion();
/** /**
* Returns the name of the image that will be built. When {@code null}, the name will * 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 * be derived from the {@link Project Project's} {@link Project#getName() name} and
@ -340,9 +328,6 @@ public abstract class BootBuildImage extends DefaultTask {
if (environment != null && !environment.isEmpty()) { if (environment != null && !environment.isEmpty()) {
request = request.withEnv(environment); request = request.withEnv(environment);
} }
if (this.getTargetJavaVersion().isPresent() && !request.getEnv().containsKey(BUILDPACK_JVM_VERSION_KEY)) {
request = request.withEnv(BUILDPACK_JVM_VERSION_KEY, translateTargetJavaVersion());
}
return request; return request;
} }
@ -401,8 +386,4 @@ public abstract class BootBuildImage extends DefaultTask {
return request; return request;
} }
private String translateTargetJavaVersion() {
return this.getTargetJavaVersion().get().getMajorVersion() + ".*";
}
} }

@ -119,7 +119,8 @@ public abstract class BootJar extends Jar implements BootArchive {
@Override @Override
public void copy() { public void copy() {
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX); CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
this.getTargetJavaVersion().get().getMajorVersion());
super.copy(); super.copy();
} }

@ -94,7 +94,8 @@ public abstract class BootWar extends War implements BootArchive {
@Override @Override
public void copy() { public void copy() {
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX); CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
this.getTargetJavaVersion().get().getMajorVersion());
super.copy(); super.copy();
} }

@ -478,6 +478,15 @@ abstract class AbstractBootArchiveIntegrationTests {
} }
} }
@TestTemplate
void javaVersionIsSetInManifest() throws IOException {
BuildResult result = this.gradleBuild.build(this.taskName);
assertThat(result.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Build-Jdk-Spec")).isNotEmpty();
}
}
private void copyMainClassApplication() throws IOException { private void copyMainClassApplication() throws IOException {
copyApplication("main"); copyApplication("main");
} }

@ -73,7 +73,6 @@ class BootBuildImageIntegrationTests {
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("env: BP_JVM_VERSION=8.*");
assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); assertThat(result.getOutput()).contains("Network status: HTTP/2 200");
assertThat(result.getOutput()).contains("---> Test Info buildpack done"); assertThat(result.getOutput()).contains("---> Test Info buildpack done");
removeImages(projectName); removeImages(projectName);
@ -88,7 +87,6 @@ class BootBuildImageIntegrationTests {
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("env: BP_JVM_VERSION=8.*");
assertThat(result.getOutput()).contains("---> Test Info buildpack done"); assertThat(result.getOutput()).contains("---> Test Info buildpack done");
File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs");
assertThat(buildLibs.listFiles()) assertThat(buildLibs.listFiles())

@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -139,27 +138,6 @@ class BootBuildImageTests {
.hasSize(2); .hasSize(2);
} }
@Test
void whenJavaVersionIsSetInEnvironmentItIsIncludedInTheRequest() {
this.buildImage.getEnvironment().put("BP_JVM_VERSION", "from-env");
this.buildImage.getTargetJavaVersion().set(JavaVersion.VERSION_1_8);
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("BP_JVM_VERSION", "from-env").hasSize(1);
}
@Test
void whenTargetCompatibilityIsSetThenJavaVersionIsIncludedInTheRequest() {
this.buildImage.getTargetJavaVersion().set(JavaVersion.VERSION_1_8);
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("BP_JVM_VERSION", "8.*").hasSize(1);
}
@Test
void whenTargetCompatibilityIsSetThenJavaVersionIsAddedToEnvironment() {
this.buildImage.getEnvironment().put("ALPHA", "a");
this.buildImage.getTargetJavaVersion().set(JavaVersion.VERSION_11);
assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a")
.containsEntry("BP_JVM_VERSION", "11.*").hasSize(2);
}
@Test @Test
void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() { void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() {
assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse(); assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse();

@ -22,7 +22,9 @@ import java.util.jar.JarFile;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -45,6 +47,11 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
"BOOT-INF/"); "BOOT-INF/");
} }
@BeforeEach
void setUp() {
this.getTask().getTargetJavaVersion().set(JavaVersion.VERSION_17);
}
@Test @Test
void contentCanBeAddedToBootInfUsingCopySpecFromGetter() throws IOException { void contentCanBeAddedToBootInfUsingCopySpecFromGetter() throws IOException {
BootJar bootJar = getTask(); BootJar bootJar = getTask();
@ -194,6 +201,14 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
} }
} }
@Test
void javaVersionIsWrittenToManifest() throws IOException {
try (JarFile jarFile = new JarFile(createPopulatedJar())) {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Build-Jdk-Spec"))
.isEqualTo(JavaVersion.VERSION_17.getMajorVersion());
}
}
@Override @Override
void applyLayered(Action<LayeredSpec> action) { void applyLayered(Action<LayeredSpec> action) {
getTask().layered(action); getTask().layered(action);

@ -21,7 +21,9 @@ import java.io.IOException;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -42,6 +44,11 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
"WEB-INF/"); "WEB-INF/");
} }
@BeforeEach
void setUp() {
this.getTask().getTargetJavaVersion().set(JavaVersion.VERSION_17);
}
@Test @Test
void providedClasspathJarsArePackagedInWebInfLibProvided() throws IOException { void providedClasspathJarsArePackagedInWebInfLibProvided() throws IOException {
getTask().getMainClass().set("com.example.Main"); getTask().getMainClass().set("com.example.Main");
@ -137,6 +144,14 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
} }
} }
@Test
void javaVersionIsWrittenToManifest() throws IOException {
try (JarFile jarFile = new JarFile(createPopulatedJar())) {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Build-Jdk-Spec"))
.isEqualTo(JavaVersion.VERSION_17.getMajorVersion());
}
}
@Override @Override
protected void executeTask() { protected void executeTask() {
getTask().copy(); getTask().copy();

@ -58,8 +58,8 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
assertThat(original).doesNotExist(); assertThat(original).doesNotExist();
assertThat(buildLog(project)).contains("Building image") assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image:0.0.1.BUILD-SNAPSHOT") .contains("docker.io/library/build-image:0.0.1.BUILD-SNAPSHOT")
.contains("---> Test Info buildpack building").contains("env: BP_JVM_VERSION=8.*") .contains("---> Test Info buildpack building").contains("---> Test Info buildpack done")
.contains("---> Test Info buildpack done").contains("Successfully built image"); .contains("Successfully built image");
removeImage("build-image", "0.0.1.BUILD-SNAPSHOT"); removeImage("build-image", "0.0.1.BUILD-SNAPSHOT");
}); });
} }
@ -75,8 +75,8 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
assertThat(classifier).doesNotExist(); assertThat(classifier).doesNotExist();
assertThat(buildLog(project)).contains("Building image") assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-classifier:0.0.1.BUILD-SNAPSHOT") .contains("docker.io/library/build-image-classifier:0.0.1.BUILD-SNAPSHOT")
.contains("---> Test Info buildpack building").contains("env: BP_JVM_VERSION=8.*") .contains("---> Test Info buildpack building").contains("---> Test Info buildpack done")
.contains("---> Test Info buildpack done").contains("Successfully built image"); .contains("Successfully built image");
removeImage("build-image-classifier", "0.0.1.BUILD-SNAPSHOT"); removeImage("build-image-classifier", "0.0.1.BUILD-SNAPSHOT");
}); });
} }

@ -68,8 +68,6 @@ import org.springframework.util.StringUtils;
@Execute(phase = LifecyclePhase.PACKAGE) @Execute(phase = LifecyclePhase.PACKAGE)
public class BuildImageMojo extends AbstractPackagerMojo { public class BuildImageMojo extends AbstractPackagerMojo {
private static final String BUILDPACK_JVM_VERSION_KEY = "BP_JVM_VERSION";
static { static {
System.setProperty("org.slf4j.simpleLogger.log.org.apache.http.wire", "ERROR"); System.setProperty("org.slf4j.simpleLogger.log.org.apache.http.wire", "ERROR");
} }
@ -293,22 +291,10 @@ public class BuildImageMojo extends AbstractPackagerMojo {
} }
private BuildRequest customize(BuildRequest request) { private BuildRequest customize(BuildRequest request) {
request = customizeEnvironment(request);
request = customizeCreator(request); request = customizeCreator(request);
return request; return request;
} }
private BuildRequest customizeEnvironment(BuildRequest request) {
if (!request.getEnv().containsKey(BUILDPACK_JVM_VERSION_KEY)) {
JavaCompilerPluginConfiguration compilerConfiguration = new JavaCompilerPluginConfiguration(this.project);
String targetJavaVersion = compilerConfiguration.getTargetMajorVersion();
if (StringUtils.hasText(targetJavaVersion)) {
return request.withEnv(BUILDPACK_JVM_VERSION_KEY, targetJavaVersion + ".*");
}
}
return request;
}
private BuildRequest customizeCreator(BuildRequest request) { private BuildRequest customizeCreator(BuildRequest request) {
String springBootVersion = VersionExtractor.forClass(BuildImageMojo.class); String springBootVersion = VersionExtractor.forClass(BuildImageMojo.class);
if (StringUtils.hasText(springBootVersion)) { if (StringUtils.hasText(springBootVersion)) {

@ -38,4 +38,5 @@ application {
bootBuildImage { bootBuildImage {
archiveFile = bootDistZip.archiveFile archiveFile = bootDistZip.archiveFile
environment = ['BP_JVM_VERSION': project.targetCompatibility.getMajorVersion()]
} }

@ -38,4 +38,5 @@ application {
bootBuildImage { bootBuildImage {
archiveFile = distZip.archiveFile archiveFile = distZip.archiveFile
environment = ['BP_JVM_VERSION': project.targetCompatibility.getMajorVersion()]
} }

@ -31,5 +31,5 @@ war {
bootBuildImage { bootBuildImage {
archiveFile = war.archiveFile archiveFile = war.archiveFile
environment = ['BP_TOMCAT_VERSION': '10.*'] environment = ['BP_JVM_VERSION': project.targetCompatibility.getMajorVersion(), 'BP_TOMCAT_VERSION': '10.*']
} }

@ -72,6 +72,7 @@ def boolean isWindows() {
task.mainClass = "com.example.ResourceHandlingApplication" task.mainClass = "com.example.ResourceHandlingApplication"
task.classpath = sourceSets.main.runtimeClasspath.plus(configurations.getByName(container)) task.classpath = sourceSets.main.runtimeClasspath.plus(configurations.getByName(container))
task.classifier = container task.classifier = container
task.targetJavaVersion = project.getTargetCompatibility()
} }
tasks.register("${container}BootJar", BootJar, configurer) tasks.register("${container}BootJar", BootJar, configurer)
tasks.register("${container}BootWar", BootWar, configurer) tasks.register("${container}BootWar", BootWar, configurer)

Loading…
Cancel
Save