Allow users to opt out of including the layer tools in a layered jar

For Maven, the layer configuration is now an additional configuration
option instead of a layout type.

Closes gh-19866
This commit is contained in:
Madhura Bhave
2020-02-07 16:02:35 -08:00
parent 56475c19fb
commit 15cd590f7f
19 changed files with 408 additions and 22 deletions

View File

@@ -287,3 +287,18 @@ The jar will then be split into layer folders which may include:
* `snapshots-dependencies`
* `dependencies`
When you create a layered jar, the `spring-boot-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:
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy
----
include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle[tags=layered]
----
[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"]
.Kotlin
----
include::../gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts[tags=layered]
----

View File

@@ -0,0 +1,16 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClassName 'com.example.ExampleApplication'
}
// tag::layered[]
bootJar {
layered {
includeLayerTools = false
}
}
// end::layered[]

View File

@@ -0,0 +1,18 @@
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
java
id("org.springframework.boot") version "{version}"
}
tasks.getByName<BootJar>("bootJar") {
mainClassName = "com.example.ExampleApplication"
}
// tag::layered[]
tasks.getByName<BootJar>("bootJar") {
layered {
includeLayerTools = false
}
}
// end::layered[]

View File

@@ -40,6 +40,8 @@ import org.gradle.api.java.archives.Attributes;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.bundling.Jar;
import org.springframework.boot.loader.tools.Layer;
@@ -67,6 +69,8 @@ public class BootJar extends Jar implements BootArchive {
private Layers layers;
private LayerConfiguration layerConfiguration;
private static final String BOOT_INF_LAYERS = "BOOT-INF/layers";
private final List<String> dependencies = new ArrayList<>();
@@ -164,20 +168,33 @@ public class BootJar extends Jar implements BootArchive {
action.execute(enableLaunchScriptIfNecessary());
}
@Optional
@Nested
@Input
public LayerConfiguration getLayerConfiguration() {
return this.layerConfiguration;
}
/**
* Configures the archive to have layers.
*/
public void layered() {
this.layers = Layers.IMPLICIT;
this.bootInf.into("lib", (spec) -> spec.from((Callable<File>) () -> {
String jarName = "spring-boot-jarmode-layertools.jar";
InputStream stream = getClass().getClassLoader().getResourceAsStream("META-INF/jarmode/" + jarName);
File taskTmp = new File(getProject().getBuildDir(), "tmp/" + getName());
taskTmp.mkdirs();
File layerToolsJar = new File(taskTmp, jarName);
FileCopyUtils.copy(stream, new FileOutputStream(layerToolsJar));
return layerToolsJar;
}));
enableLayers();
applyLayers();
}
private void applyLayers() {
if (this.layerConfiguration.isIncludeLayerTools()) {
this.bootInf.into("lib", (spec) -> spec.from((Callable<File>) () -> {
String jarName = "spring-boot-jarmode-layertools.jar";
InputStream stream = getClass().getClassLoader().getResourceAsStream("META-INF/jarmode/" + jarName);
File taskTmp = new File(getProject().getBuildDir(), "tmp/" + getName());
taskTmp.mkdirs();
File layerToolsJar = new File(taskTmp, jarName);
FileCopyUtils.copy(stream, new FileOutputStream(layerToolsJar));
return layerToolsJar;
}));
}
this.bootInf.eachFile((details) -> {
Layer layer = layerForFileDetails(details);
if (layer != null) {
@@ -188,6 +205,20 @@ public class BootJar extends Jar implements BootArchive {
this.bootInf.into("", (spec) -> spec.from(createLayersIndex()));
}
public void layered(Action<LayerConfiguration> action) {
action.execute(enableLayers());
applyLayers();
}
private LayerConfiguration enableLayers() {
this.layers = Layers.IMPLICIT;
if (this.layerConfiguration == null) {
this.layerConfiguration = new LayerConfiguration();
}
return this.layerConfiguration;
}
private Layer layerForFileDetails(FileCopyDetails details) {
String path = details.getPath();
if (path.startsWith("BOOT-INF/lib/")) {

View File

@@ -0,0 +1,44 @@
/*
* 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.gradle.tasks.bundling;
import org.gradle.api.tasks.Input;
/**
* Encapsulates the configuration for a layered jar.
*
* @author Madhura Bhave
* @since 2.3.0
*/
public class LayerConfiguration {
private boolean includeLayerTools = true;
/**
* Whether to include the layer tools jar.
* @return true if layer tools is included
*/
@Input
public boolean isIncludeLayerTools() {
return this.includeLayerTools;
}
public void setIncludeLayerTools(boolean includeLayerTools) {
this.includeLayerTools = includeLayerTools;
}
}

View File

@@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Integration tests for {@link BootJar}.
*
* @author Andy Wilkinson
* @author Madhura Bhave
*/
class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
@@ -53,4 +54,13 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
.isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void notUpToDateWhenBuiltWithLayersAndToolsAndThenWithLayersAndWithoutTools()
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure, IOException {
assertThat(this.gradleBuild.build("-Playered=true", "bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-Playered=true", "-PexcludeTools=true", "bootJar").task(":bootJar")
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
}

View File

@@ -25,6 +25,7 @@ import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.gradle.api.Action;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -129,6 +130,13 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
assertThat(entryNames).contains("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
}
@Test
void whenJarIsLayeredAndIncludeLayerToolsIsFalseThenLayerToolsAreNotAddedToTheJar() throws IOException {
List<String> entryNames = getEntryNames(
createLayeredJar((configuration) -> configuration.setIncludeLayerTools(false)));
assertThat(entryNames).doesNotContain("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
}
@Test
void classpathIndexPointsToBootInfLibs() throws IOException {
try (JarFile jarFile = new JarFile(createPopulatedJar())) {
@@ -145,13 +153,22 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
return getTask().getArchiveFile().get().getAsFile();
}
private File createLayeredJar() throws IOException {
private File createLayeredJar(Action<LayerConfiguration> action) throws IOException {
addContent();
getTask().layered();
if (action != null) {
getTask().layered(action);
}
else {
getTask().layered();
}
executeTask();
return getTask().getArchiveFile().get().getAsFile();
}
private File createLayeredJar() throws IOException {
return createLayeredJar(null);
}
private void addContent() throws IOException {
BootJar bootJar = getTask();
bootJar.setMainClassName("com.example.Main");

View File

@@ -11,6 +11,8 @@ bootJar {
}
}
if (project.hasProperty('layered') && project.getProperty('layered')) {
layered()
layered {
includeLayerTools = project.hasProperty('excludeTools') && project.getProperty('excludeTools') ? false : true
}
}
}