Switch layering on by default
Closes gh-20983
This commit is contained in:
@@ -275,19 +275,6 @@ By default, the `bootJar` task builds an archive that contains the application's
|
||||
For cases where a docker image needs to be built from the contents of the jar, it's useful to be able to separate these directories further so that they can be written into distinct layers.
|
||||
|
||||
Layered jars use the same layout as regular boot packaged jars, but include an additional meta-data file that describes each layer.
|
||||
To use this feature, the layering feature must be enabled:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Groovy
|
||||
----
|
||||
include::../gradle/packaging/boot-jar-layered.gradle[tags=layered]
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
include::../gradle/packaging/boot-jar-layered.gradle.kts[tags=layered]
|
||||
----
|
||||
|
||||
By default, the following layers are defined:
|
||||
|
||||
@@ -300,7 +287,21 @@ The layers order is important as it determines how likely previous layers can be
|
||||
The default order is `dependencies`, `spring-boot-loader`, `snapshot-dependencies`, `application`.
|
||||
Content that is least likely to change should be added first, followed by layers that are more likely to change.
|
||||
|
||||
When you create a layered jar, the `spring-boot-jarmode-layertools` jar will be added as a dependency to your jar.
|
||||
To disable this feature, you can do so in the following manner:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Groovy
|
||||
----
|
||||
include::../gradle/packaging/boot-jar-layered-disabled.gradle[tags=layered]
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
include::../gradle/packaging/boot-jar-layered-disabled.gradle.kts[tags=layered]
|
||||
----
|
||||
|
||||
When a layered jar is created, the `spring-boot-jarmode-layertools` jar will be added as a dependency to your jar.
|
||||
With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers.
|
||||
If you wish to exclude this dependency, you can do so in the following manner:
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ bootJar {
|
||||
|
||||
// tag::layered[]
|
||||
bootJar {
|
||||
layered()
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
// end::layered[]
|
||||
@@ -11,6 +11,8 @@ tasks.getByName<BootJar>("bootJar") {
|
||||
|
||||
// tag::layered[]
|
||||
tasks.getByName<BootJar>("bootJar") {
|
||||
layered()
|
||||
layered {
|
||||
isEnabled = false
|
||||
}
|
||||
}
|
||||
// end::layered[]
|
||||
@@ -64,7 +64,7 @@ public class BootJar extends Jar implements BootArchive {
|
||||
|
||||
private FileCollection classpath;
|
||||
|
||||
private LayeredSpec layered;
|
||||
private LayeredSpec layered = new LayeredSpec();
|
||||
|
||||
/**
|
||||
* Creates a new {@code BootJar} task.
|
||||
@@ -98,13 +98,17 @@ public class BootJar extends Jar implements BootArchive {
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_DIRECTORY, LIB_DIRECTORY,
|
||||
CLASSPATH_INDEX, (this.layered != null) ? LAYERS_INDEX : null);
|
||||
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX);
|
||||
super.copy();
|
||||
}
|
||||
|
||||
private boolean isLayeredDisabled() {
|
||||
return this.layered != null && !this.layered.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CopyAction createCopyAction() {
|
||||
if (this.layered != null) {
|
||||
if (!isLayeredDisabled()) {
|
||||
JavaPluginConvention javaPluginConvention = getProject().getConvention()
|
||||
.findPlugin(JavaPluginConvention.class);
|
||||
Iterable<SourceSet> sourceSets = (javaPluginConvention != null) ? javaPluginConvention.getSourceSets()
|
||||
|
||||
@@ -52,6 +52,8 @@ public class LayeredSpec {
|
||||
|
||||
private boolean includeLayerTools = true;
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private ApplicationSpec application = new ApplicationSpec();
|
||||
|
||||
private DependenciesSpec dependencies = new DependenciesSpec();
|
||||
@@ -80,6 +82,24 @@ public class LayeredSpec {
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the layers.idx should be included in the jar.
|
||||
* @return whether the layers.idx should be included
|
||||
*/
|
||||
@Input
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the layers.idx should be included in the jar.
|
||||
* @param enabled {@code true} layers.idx should be included in the jar, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ApplicationSpec} that controls the layers to which application
|
||||
* classes and resources belong.
|
||||
|
||||
@@ -180,16 +180,14 @@ class PackagingDocumentationTests {
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void bootJarLayered() throws IOException {
|
||||
this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered").build("bootJar");
|
||||
void bootJarLayeredDisabled() throws IOException {
|
||||
this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered-disabled").build("bootJar");
|
||||
File file = new File(this.gradleBuild.getProjectDir(),
|
||||
"build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar");
|
||||
assertThat(file).isFile();
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
JarEntry entry = jar.getJarEntry("BOOT-INF/layers.idx");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(Collections.list(jar.entries()).stream().map(JarEntry::getName)
|
||||
.filter((name) -> name.startsWith("BOOT-INF/lib/spring-boot"))).isNotEmpty();
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,26 +63,34 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||
|
||||
@TestTemplate
|
||||
void upToDateWhenBuiltTwiceWithLayers() throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.build("-Playered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
.isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.build("-Playered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
.isEqualTo(TaskOutcome.UP_TO_DATE);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
.isEqualTo(TaskOutcome.UP_TO_DATE);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenBuiltWithoutLayersAndThenWithLayers()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.build("-Playered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "-PdisableLayers=true", "bootJar").task(":bootJar")
|
||||
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
.isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenBuiltWithLayersAndToolsAndThenWithLayersAndWithoutTools()
|
||||
void notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.build("-Playered=true", "bootJar").task(":bootJar").getOutcome())
|
||||
.isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.build("-Playered=true", "-PexcludeTools=true", "bootJar").task(":bootJar")
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "-PexcludeTools=true", "bootJar").task(":bootJar")
|
||||
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,30 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarShouldBeLayeredByDefault() throws IOException {
|
||||
addContent();
|
||||
executeTask();
|
||||
BootJar bootJar = getTask();
|
||||
try (JarFile jarFile = new JarFile(bootJar.getArchiveFile().get().getAsFile())) {
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes"))
|
||||
.isEqualTo("BOOT-INF/classes/");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib"))
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index"))
|
||||
.isEqualTo("BOOT-INF/classpath.idx");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index"))
|
||||
.isEqualTo("BOOT-INF/layers.idx");
|
||||
assertThat(getEntryNames(jarFile)).contains("BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarWhenLayersDisabledShouldNotContainLayersIndex() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar((configuration) -> configuration.setEnabled(false)));
|
||||
assertThat(entryNames).doesNotContain("BOOT-INF/layers.idx");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredThenManifestContainsEntryForLayersIndexInPlaceOfClassesAndLib() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
|
||||
@@ -15,3 +15,9 @@ dependencies {
|
||||
developmentOnly("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("commons-io:commons-io:2.6")
|
||||
}
|
||||
|
||||
bootJar {
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,9 @@ dependencies {
|
||||
developmentOnly("commons-io:commons-io:2.6")
|
||||
implementation("commons-io:commons-io:2.6")
|
||||
}
|
||||
|
||||
bootJar {
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,3 +19,9 @@ dependencies {
|
||||
bootJar {
|
||||
classpath configurations.developmentOnly
|
||||
}
|
||||
|
||||
bootJar {
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ bootJar {
|
||||
properties 'prop' : project.hasProperty('launchScriptProperty') ? launchScriptProperty : 'default'
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('layered') && project.getProperty('layered')) {
|
||||
if (project.hasProperty('customizeLayered') && project.getProperty('customizeLayered')) {
|
||||
layered {
|
||||
includeLayerTools = project.hasProperty('excludeTools') && project.getProperty('excludeTools') ? false : true
|
||||
enabled = project.hasProperty('disableLayers') && project.getProperty('disableLayers') ? false : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user