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
This commit is contained in:
Scott Frederick 2022-10-24 18:35:39 -05:00
parent 383d6c897f
commit c22e76632c
20 changed files with 72 additions and 67 deletions

View File

@ -150,6 +150,8 @@ final class JavaPluginAction implements PluginApplicationAction {
.provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class"));
bootJar.getMainClass().convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent()
? 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.setGroup(BasePlugin.BUILD_GROUP);
buildImage.getArchiveFile().set(bootJar.get().getArchiveFile());
buildImage.getTargetJavaVersion()
.set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility()));
});
}

View File

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

View File

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

View File

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

View File

@ -22,7 +22,6 @@ import java.util.Map;
import org.gradle.api.Action;
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;
@ -66,8 +65,6 @@ import org.springframework.util.StringUtils;
@DisableCachingByDefault
public abstract class BootBuildImage extends DefaultTask {
private static final String BUILDPACK_JVM_VERSION_KEY = "BP_JVM_VERSION";
private final Property<PullPolicy> pullPolicy;
private final String projectName;
@ -107,15 +104,6 @@ public abstract class BootBuildImage extends DefaultTask {
@PathSensitive(PathSensitivity.RELATIVE)
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
* 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()) {
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;
}
@ -401,8 +386,4 @@ public abstract class BootBuildImage extends DefaultTask {
return request;
}
private String translateTargetJavaVersion() {
return this.getTargetJavaVersion().get().getMajorVersion() + ".*";
}
}

View File

@ -119,7 +119,8 @@ public abstract class BootJar extends Jar implements BootArchive {
@Override
public void copy() {
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();
}

View File

@ -94,7 +94,8 @@ public abstract class BootWar extends War implements BootArchive {
@Override
public void copy() {
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();
}

View File

@ -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 {
copyApplication("main");
}

View File

@ -73,7 +73,6 @@ class BootBuildImageIntegrationTests {
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
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("---> Test Info buildpack done");
removeImages(projectName);
@ -88,7 +87,6 @@ class BootBuildImageIntegrationTests {
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
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");
File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs");
assertThat(buildLibs.listFiles())

View File

@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -139,27 +138,6 @@ class BootBuildImageTests {
.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
void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() {
assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse();

View File

@ -22,7 +22,9 @@ import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
import org.gradle.api.artifacts.Configuration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -45,6 +47,11 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
"BOOT-INF/");
}
@BeforeEach
void setUp() {
this.getTask().getTargetJavaVersion().set(JavaVersion.VERSION_17);
}
@Test
void contentCanBeAddedToBootInfUsingCopySpecFromGetter() throws IOException {
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
void applyLayered(Action<LayeredSpec> action) {
getTask().layered(action);

View File

@ -21,7 +21,9 @@ import java.io.IOException;
import java.util.jar.JarFile;
import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
import org.gradle.api.artifacts.Configuration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
@ -42,6 +44,11 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
"WEB-INF/");
}
@BeforeEach
void setUp() {
this.getTask().getTargetJavaVersion().set(JavaVersion.VERSION_17);
}
@Test
void providedClasspathJarsArePackagedInWebInfLibProvided() throws IOException {
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
protected void executeTask() {
getTask().copy();

View File

@ -58,8 +58,8 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
assertThat(original).doesNotExist();
assertThat(buildLog(project)).contains("Building image")
.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 done").contains("Successfully built image");
.contains("---> Test Info buildpack building").contains("---> Test Info buildpack done")
.contains("Successfully built image");
removeImage("build-image", "0.0.1.BUILD-SNAPSHOT");
});
}
@ -75,8 +75,8 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
assertThat(classifier).doesNotExist();
assertThat(buildLog(project)).contains("Building image")
.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 done").contains("Successfully built image");
.contains("---> Test Info buildpack building").contains("---> Test Info buildpack done")
.contains("Successfully built image");
removeImage("build-image-classifier", "0.0.1.BUILD-SNAPSHOT");
});
}

View File

@ -68,8 +68,6 @@ import org.springframework.util.StringUtils;
@Execute(phase = LifecyclePhase.PACKAGE)
public class BuildImageMojo extends AbstractPackagerMojo {
private static final String BUILDPACK_JVM_VERSION_KEY = "BP_JVM_VERSION";
static {
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) {
request = customizeEnvironment(request);
request = customizeCreator(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) {
String springBootVersion = VersionExtractor.forClass(BuildImageMojo.class);
if (StringUtils.hasText(springBootVersion)) {

View File

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

View File

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

View File

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

View File

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