From 34e602652c29924b5806ee0582f4ff2bef31f502 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 3 Apr 2020 10:47:57 +0100 Subject: [PATCH] Polish new layered jar support --- .../packaging/boot-jar-layered-custom.gradle | 2 +- .../boot-jar-layered-custom.gradle.kts | 8 +- .../boot-jar-layered-exclude-tools.gradle | 2 +- .../boot-jar-layered-exclude-tools.gradle.kts | 4 +- .../boot/gradle/tasks/bundling/BootJar.java | 39 ++++-- .../gradle/tasks/bundling/LayeredSpec.java | 124 ++++++++++++++++-- .../docs/PackagingDocumentationTests.java | 31 +++++ .../gradle/tasks/bundling/BootJarTests.java | 2 +- ...ootJarIntegrationTests-customLayers.gradle | 2 +- .../boot/loader/tools/StandardLayers.java | 2 + 10 files changed, 182 insertions(+), 34 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle index d0465880e9..dea2f0c059 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle @@ -22,7 +22,7 @@ bootJar { } intoLayer("dependencies") } - layerOrder "dependencies", "spring-boot-loader", "snapshot-dependencies", "application" + layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"] } } // end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts index a71a1aca7b..3cb6a6f4f0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-custom.gradle.kts @@ -5,6 +5,10 @@ plugins { id("org.springframework.boot") version "{version}" } +tasks.getByName("bootJar") { + mainClassName = "com.example.ExampleApplication" +} + // tag::layered[] tasks.getByName("bootJar") { layered { @@ -18,9 +22,9 @@ tasks.getByName("bootJar") { intoLayer("snapshot-dependencies") { include("*:*:*SNAPSHOT") } - intoLayer("dependencies") { + intoLayer("dependencies") } - layersOrder("dependencies", "spring-boot-loader", "snapshot-dependencies", "application") + layerOrder = listOf("dependencies", "spring-boot-loader", "snapshot-dependencies", "application") } } // end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle index 3b8902309a..4fe2da44f4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle @@ -9,7 +9,7 @@ bootJar { // tag::layered[] bootJar { - layers { + layered { includeLayerTools = false } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts index 7f2a5e9976..dc06abf1c7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-jar-layered-exclude-tools.gradle.kts @@ -11,8 +11,8 @@ tasks.getByName("bootJar") { // tag::layered[] tasks.getByName("bootJar") { - layers { - includeLayerTools = false + layered { + isIncludeLayerTools = false } } // end::layered[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index d6dd6ae6d1..3f74fe02e6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -20,7 +20,6 @@ import java.io.File; import java.util.Collections; import java.util.concurrent.Callable; -import groovy.lang.Closure; import org.gradle.api.Action; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.CopySpec; @@ -33,7 +32,6 @@ 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.gradle.util.ConfigureUtil; /** * A custom {@link Jar} task that produces a Spring Boot executable jar. @@ -158,28 +156,33 @@ public class BootJar extends Jar implements BootArchive { action.execute(enableLaunchScriptIfNecessary()); } + /** + * Returns the spec that describes the layers in a layerd jar. + * @return the spec for the layers or {@code null}. + * @since 2.3.0 + */ @Nested @Optional public LayeredSpec getLayered() { return this.layered; } + /** + * Configures the jar to be layered using the default layering. + * @since 2.3.0 + */ public void layered() { - layered(true); - } - - public void layered(boolean layered) { - this.layered = layered ? new LayeredSpec() : null; - } - - public void layered(Closure closure) { - layered(ConfigureUtil.configureUsing(closure)); + enableLayeringIfNecessary(); } + /** + * Configures the jar to be layered, customizing the layers using the given + * {@code action}. + * @param action the action to apply + * @since 2.3.0 + */ public void layered(Action action) { - LayeredSpec layered = new LayeredSpec(); - action.execute(layered); - this.layered = layered; + action.execute(enableLayeringIfNecessary()); } @Override @@ -258,6 +261,7 @@ public class BootJar extends Jar implements BootArchive { * {@code BOOT-INF/lib} is considered to be a library. * @param details the file copy details * @return {@code true} if the details are for a library + * @since 2.3.0 */ protected boolean isLibrary(FileCopyDetails details) { String path = details.getRelativePath().getPathString(); @@ -273,6 +277,13 @@ public class BootJar extends Jar implements BootArchive { return launchScript; } + private LayeredSpec enableLayeringIfNecessary() { + if (this.layered == null) { + this.layered = new LayeredSpec(); + } + return this.layered; + } + /** * Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read. * @param the result type diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java index c3c1c41bbc..dcec9252d8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayeredSpec.java @@ -61,59 +61,117 @@ public class LayeredSpec { private Layers layers; + /** + * Returns whether the layer tools should be included as a dependency in the layered + * jar. + * @return whether the layer tools should be included + */ @Input public boolean isIncludeLayerTools() { return this.includeLayerTools; } + /** + * Sets whether the layer tools should be included as a dependency in the layered jar. + * @param includeLayerTools {@code true} if the layer tools should be included, + * otherwise {@code false} + */ public void setIncludeLayerTools(boolean includeLayerTools) { this.includeLayerTools = includeLayerTools; } + /** + * Returns the {@link ApplicationSpec} that controls the layers to which application + * classes and resources belong. + * @return the application spec + */ @Input public ApplicationSpec getApplication() { return this.application; } - public void application(ApplicationSpec spec) { + /** + * Sets the {@link ApplicationSpec} that controls the layers to which application + * classes are resources belong. + * @param spec the application spec + */ + public void setApplication(ApplicationSpec spec) { this.application = spec; } - public void application(Closure closure) { - application(ConfigureUtil.configureUsing(closure)); - } - + /** + * Customizes the {@link ApplicationSpec} using the given {@code action}. + * @param action the action + */ public void application(Action action) { action.execute(this.application); } + /** + * Customizes the {@link ApplicationSpec} using the given {@code closure}. + * @param closure the closure + */ + public void application(Closure closure) { + application(ConfigureUtil.configureUsing(closure)); + } + + /** + * Returns the {@link DependenciesSpec} that controls the layers to which dependencies + * belong. + * @return the dependencies spec + */ @Input public DependenciesSpec getDependencies() { return this.dependencies; } - public void dependencies(DependenciesSpec spec) { + /** + * Sets the {@link DependenciesSpec} that controls the layers to which dependencies + * belong. + * @param spec the dependencies spec + */ + public void setDependencies(DependenciesSpec spec) { this.dependencies = spec; } - public void dependencies(Closure closure) { - dependencies(ConfigureUtil.configureUsing(closure)); - } - + /** + * Customizes the {@link DependenciesSpec} using the given {@code action}. + * @param action the action + */ public void dependencies(Action action) { action.execute(this.dependencies); } + /** + * Customizes the {@link DependenciesSpec} using the given {@code closure}. + * @param closure the closure + */ + public void dependencies(Closure closure) { + dependencies(ConfigureUtil.configureUsing(closure)); + } + + /** + * Returns the order of the layers in the jar from least to most frequently changing. + * @return the layer order + */ @Input public List getLayerOrder() { return this.layerOrder; } - public void layerOrder(String... layerOrder) { + /** + * Sets to order of the layers in the jar from least to most frequently changing. + * @param layerOrder the layer order + */ + public void setLayerOrder(String... layerOrder) { this.layerOrder = Arrays.asList(layerOrder); } - public void layerOrder(List layerOrder) { + /** + * Sets to order of the layers in the jar from least to most frequently changing. + * @param layerOrder the layer order + */ + public void setLayerOrder(List layerOrder) { this.layerOrder = layerOrder; } @@ -141,6 +199,10 @@ public class LayeredSpec { return new CustomLayers(layers, this.application.asSelectors(), this.dependencies.asSelectors()); } + /** + * Base class for specs that control the layers to which a category of content should + * belong. + */ public abstract static class IntoLayersSpec implements Serializable { private final List intoLayers; @@ -174,6 +236,9 @@ public class LayeredSpec { } + /** + * Spec that controls the content that should be part of a particular layer. + */ public static class IntoLayerSpec implements Serializable { private final String intoLayer; @@ -182,14 +247,33 @@ public class LayeredSpec { private final List excludes = new ArrayList<>(); + /** + * Creates a new {@code IntoLayerSpec} that will control the content of the given + * layer. + * @param intoLayer the layer + */ public IntoLayerSpec(String intoLayer) { this.intoLayer = intoLayer; } + /** + * Adds patterns that control the content that is included in the layer. If no + * includes are specified then all content is included. If includes are specified + * then content must match an inclusion pattern and not match any exclusion + * patterns to be included. + * @param patterns the patterns to be included + */ public void include(String... patterns) { this.includes.addAll(Arrays.asList(patterns)); } + /** + * Adds patterns that control the content that is excluded from the layer. If no + * excludes a specified no content is excluded. If exclusions are specified then + * any content that matches an exclusion will be excluded irrespective of whether + * it matches an include pattern. + * @param patterns the patterns to be excluded + */ public void exclude(String... patterns) { this.includes.addAll(Arrays.asList(patterns)); } @@ -201,8 +285,17 @@ public class LayeredSpec { } + /** + * An {@link IntoLayersSpec} that controls the layers to which application classes and + * resources belong. + */ public static class ApplicationSpec extends IntoLayersSpec { + /** + * Creates a new {@code ApplicationSpec} with the given {@code contents}. + * @param contents specs for the layers in which application content should be + * included + */ public ApplicationSpec(IntoLayerSpec... contents) { super(contents); } @@ -213,8 +306,15 @@ public class LayeredSpec { } + /** + * An {@link IntoLayersSpec} that controls the layers to which dependencies belong. + */ public static class DependenciesSpec extends IntoLayersSpec { + /** + * Creates a new {@code DependenciesSpec} with the given {@code contents}. + * @param contents specs for the layers in which dependencies should be included + */ public DependenciesSpec(IntoLayerSpec... contents) { super(contents); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java index 95d3db5247..6be656843a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java @@ -21,6 +21,7 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.util.Collections; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -187,6 +188,36 @@ class PackagingDocumentationTests { 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(); + } + } + + @TestTemplate + void bootJarLayeredCustom() throws IOException { + this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered-custom").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(); + } + } + + @TestTemplate + void bootJarLayeredExcludeTools() throws IOException { + this.gradleBuild.script("src/docs/gradle/packaging/boot-jar-layered-exclude-tools").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"))).isEmpty(); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java index 058d3b1d9a..48a16d7c19 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java @@ -124,7 +124,7 @@ class BootJarTests extends AbstractBootArchiveTests { dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*")); dependencies.intoLayer("my-deps"); }); - layered.layerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application"); + layered.setLayerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application"); }); try (JarFile jarFile = new JarFile(jar)) { List entryNames = getEntryNames(jar); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle index 15cce1e639..22f6f433b8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-customLayers.gradle @@ -21,7 +21,7 @@ bootJar { } intoLayer("dependencies") } - layerOrder "dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app" + layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"] } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/StandardLayers.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/StandardLayers.java index b55e777814..544c213f3a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/StandardLayers.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/StandardLayers.java @@ -26,6 +26,8 @@ import java.util.stream.Stream; * Base class for the standard set of {@link Layers}. Defines the following layers: *
    *
  1. "dependencies" - For non snapshot dependencies
  2. + *
  3. "spring-boot-loader" - For classes from {@code spring-boot-loader} used to launch a + * fat jar
  4. *
  5. "snapshot-dependencies" - For snapshot dependencies
  6. *
  7. "application" - For application classes and resources
  8. *