Commit 34e60265 authored by Andy Wilkinson's avatar Andy Wilkinson

Polish new layered jar support

parent 3e936dd7
......@@ -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[]
......@@ -5,6 +5,10 @@ plugins {
id("org.springframework.boot") version "{version}"
}
tasks.getByName<BootJar>("bootJar") {
mainClassName = "com.example.ExampleApplication"
}
// tag::layered[]
tasks.getByName<BootJar>("bootJar") {
layered {
......@@ -18,9 +22,9 @@ tasks.getByName<BootJar>("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[]
......@@ -9,7 +9,7 @@ bootJar {
// tag::layered[]
bootJar {
layers {
layered {
includeLayerTools = false
}
}
......
......@@ -11,8 +11,8 @@ tasks.getByName<BootJar>("bootJar") {
// tag::layered[]
tasks.getByName<BootJar>("bootJar") {
layers {
includeLayerTools = false
layered {
isIncludeLayerTools = false
}
}
// end::layered[]
......@@ -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<LayeredSpec> 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 <T> the result type
......
......@@ -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<ApplicationSpec> 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<DependenciesSpec> 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<String> 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<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(List<String> 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<IntoLayerSpec> 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<String> 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);
}
......
......@@ -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();
}
}
......
......@@ -124,7 +124,7 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
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<String> entryNames = getEntryNames(jar);
......
......@@ -21,7 +21,7 @@ bootJar {
}
intoLayer("dependencies")
}
layerOrder "dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"
layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"]
}
}
......
......@@ -26,6 +26,8 @@ import java.util.stream.Stream;
* Base class for the standard set of {@link Layers}. Defines the following layers:
* <ol>
* <li>"dependencies" - For non snapshot dependencies</li>
* <li>"spring-boot-loader" - For classes from {@code spring-boot-loader} used to launch a
* fat jar</li>
* <li>"snapshot-dependencies" - For snapshot dependencies</li>
* <li>"application" - For application classes and resources</li>
* </ol>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment