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

Polish new layered jar support

parent 3e936dd7
...@@ -22,7 +22,7 @@ bootJar { ...@@ -22,7 +22,7 @@ bootJar {
} }
intoLayer("dependencies") intoLayer("dependencies")
} }
layerOrder "dependencies", "spring-boot-loader", "snapshot-dependencies", "application" layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"]
} }
} }
// end::layered[] // end::layered[]
...@@ -5,6 +5,10 @@ plugins { ...@@ -5,6 +5,10 @@ plugins {
id("org.springframework.boot") version "{version}" id("org.springframework.boot") version "{version}"
} }
tasks.getByName<BootJar>("bootJar") {
mainClassName = "com.example.ExampleApplication"
}
// tag::layered[] // tag::layered[]
tasks.getByName<BootJar>("bootJar") { tasks.getByName<BootJar>("bootJar") {
layered { layered {
...@@ -18,9 +22,9 @@ tasks.getByName<BootJar>("bootJar") { ...@@ -18,9 +22,9 @@ tasks.getByName<BootJar>("bootJar") {
intoLayer("snapshot-dependencies") { intoLayer("snapshot-dependencies") {
include("*:*:*SNAPSHOT") 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[] // end::layered[]
...@@ -9,7 +9,7 @@ bootJar { ...@@ -9,7 +9,7 @@ bootJar {
// tag::layered[] // tag::layered[]
bootJar { bootJar {
layers { layered {
includeLayerTools = false includeLayerTools = false
} }
} }
......
...@@ -11,8 +11,8 @@ tasks.getByName<BootJar>("bootJar") { ...@@ -11,8 +11,8 @@ tasks.getByName<BootJar>("bootJar") {
// tag::layered[] // tag::layered[]
tasks.getByName<BootJar>("bootJar") { tasks.getByName<BootJar>("bootJar") {
layers { layered {
includeLayerTools = false isIncludeLayerTools = false
} }
} }
// end::layered[] // end::layered[]
...@@ -20,7 +20,6 @@ import java.io.File; ...@@ -20,7 +20,6 @@ import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import groovy.lang.Closure;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.CopySpec; import org.gradle.api.file.CopySpec;
...@@ -33,7 +32,6 @@ import org.gradle.api.tasks.Internal; ...@@ -33,7 +32,6 @@ import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.bundling.Jar;
import org.gradle.util.ConfigureUtil;
/** /**
* A custom {@link Jar} task that produces a Spring Boot executable jar. * A custom {@link Jar} task that produces a Spring Boot executable jar.
...@@ -158,28 +156,33 @@ public class BootJar extends Jar implements BootArchive { ...@@ -158,28 +156,33 @@ public class BootJar extends Jar implements BootArchive {
action.execute(enableLaunchScriptIfNecessary()); 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 @Nested
@Optional @Optional
public LayeredSpec getLayered() { public LayeredSpec getLayered() {
return this.layered; return this.layered;
} }
/**
* Configures the jar to be layered using the default layering.
* @since 2.3.0
*/
public void layered() { public void layered() {
layered(true); enableLayeringIfNecessary();
}
public void layered(boolean layered) {
this.layered = layered ? new LayeredSpec() : null;
}
public void layered(Closure<?> closure) {
layered(ConfigureUtil.configureUsing(closure));
} }
/**
* 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) { public void layered(Action<LayeredSpec> action) {
LayeredSpec layered = new LayeredSpec(); action.execute(enableLayeringIfNecessary());
action.execute(layered);
this.layered = layered;
} }
@Override @Override
...@@ -258,6 +261,7 @@ public class BootJar extends Jar implements BootArchive { ...@@ -258,6 +261,7 @@ public class BootJar extends Jar implements BootArchive {
* {@code BOOT-INF/lib} is considered to be a library. * {@code BOOT-INF/lib} is considered to be a library.
* @param details the file copy details * @param details the file copy details
* @return {@code true} if the details are for a library * @return {@code true} if the details are for a library
* @since 2.3.0
*/ */
protected boolean isLibrary(FileCopyDetails details) { protected boolean isLibrary(FileCopyDetails details) {
String path = details.getRelativePath().getPathString(); String path = details.getRelativePath().getPathString();
...@@ -273,6 +277,13 @@ public class BootJar extends Jar implements BootArchive { ...@@ -273,6 +277,13 @@ public class BootJar extends Jar implements BootArchive {
return launchScript; 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. * Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
* @param <T> the result type * @param <T> the result type
......
...@@ -61,59 +61,117 @@ public class LayeredSpec { ...@@ -61,59 +61,117 @@ public class LayeredSpec {
private Layers layers; 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 @Input
public boolean isIncludeLayerTools() { public boolean isIncludeLayerTools() {
return this.includeLayerTools; 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) { public void setIncludeLayerTools(boolean includeLayerTools) {
this.includeLayerTools = includeLayerTools; this.includeLayerTools = includeLayerTools;
} }
/**
* Returns the {@link ApplicationSpec} that controls the layers to which application
* classes and resources belong.
* @return the application spec
*/
@Input @Input
public ApplicationSpec getApplication() { public ApplicationSpec getApplication() {
return this.application; 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; 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) { public void application(Action<ApplicationSpec> action) {
action.execute(this.application); 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 @Input
public DependenciesSpec getDependencies() { public DependenciesSpec getDependencies() {
return this.dependencies; 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; 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) { public void dependencies(Action<DependenciesSpec> action) {
action.execute(this.dependencies); 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 @Input
public List<String> getLayerOrder() { public List<String> getLayerOrder() {
return this.layerOrder; 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); 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; this.layerOrder = layerOrder;
} }
...@@ -141,6 +199,10 @@ public class LayeredSpec { ...@@ -141,6 +199,10 @@ public class LayeredSpec {
return new CustomLayers(layers, this.application.asSelectors(), this.dependencies.asSelectors()); 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 { public abstract static class IntoLayersSpec implements Serializable {
private final List<IntoLayerSpec> intoLayers; private final List<IntoLayerSpec> intoLayers;
...@@ -174,6 +236,9 @@ public class LayeredSpec { ...@@ -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 { public static class IntoLayerSpec implements Serializable {
private final String intoLayer; private final String intoLayer;
...@@ -182,14 +247,33 @@ public class LayeredSpec { ...@@ -182,14 +247,33 @@ public class LayeredSpec {
private final List<String> excludes = new ArrayList<>(); 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) { public IntoLayerSpec(String intoLayer) {
this.intoLayer = 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) { public void include(String... patterns) {
this.includes.addAll(Arrays.asList(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) { public void exclude(String... patterns) {
this.includes.addAll(Arrays.asList(patterns)); this.includes.addAll(Arrays.asList(patterns));
} }
...@@ -201,8 +285,17 @@ public class LayeredSpec { ...@@ -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 { 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) { public ApplicationSpec(IntoLayerSpec... contents) {
super(contents); super(contents);
} }
...@@ -213,8 +306,15 @@ public class LayeredSpec { ...@@ -213,8 +306,15 @@ public class LayeredSpec {
} }
/**
* An {@link IntoLayersSpec} that controls the layers to which dependencies belong.
*/
public static class DependenciesSpec extends IntoLayersSpec { 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) { public DependenciesSpec(IntoLayerSpec... contents) {
super(contents); super(contents);
} }
......
...@@ -21,6 +21,7 @@ import java.io.FileOutputStream; ...@@ -21,6 +21,7 @@ import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
...@@ -187,6 +188,36 @@ class PackagingDocumentationTests { ...@@ -187,6 +188,36 @@ class PackagingDocumentationTests {
try (JarFile jar = new JarFile(file)) { try (JarFile jar = new JarFile(file)) {
JarEntry entry = jar.getJarEntry("BOOT-INF/layers.idx"); JarEntry entry = jar.getJarEntry("BOOT-INF/layers.idx");
assertThat(entry).isNotNull(); 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> { ...@@ -124,7 +124,7 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*")); dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*"));
dependencies.intoLayer("my-deps"); 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)) { try (JarFile jarFile = new JarFile(jar)) {
List<String> entryNames = getEntryNames(jar); List<String> entryNames = getEntryNames(jar);
......
...@@ -21,7 +21,7 @@ bootJar { ...@@ -21,7 +21,7 @@ bootJar {
} }
intoLayer("dependencies") 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; ...@@ -26,6 +26,8 @@ import java.util.stream.Stream;
* Base class for the standard set of {@link Layers}. Defines the following layers: * Base class for the standard set of {@link Layers}. Defines the following layers:
* <ol> * <ol>
* <li>"dependencies" - For non snapshot dependencies</li> * <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>"snapshot-dependencies" - For snapshot dependencies</li>
* <li>"application" - For application classes and resources</li> * <li>"application" - For application classes and resources</li>
* </ol> * </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