Commit bfa04e65 authored by Andy Wilkinson's avatar Andy Wilkinson Committed by Phillip Webb

Support flat jar layering with Gradle

Update the Gralde plugin so that layered jars now use the regular "flat"
format. The layers.idx file now describes which layer each file should
be placed.

See gh-20813
Co-authored-by: 's avatarPhillip Webb <pwebb@pivotal.io>
parent 4e3cdf93
......@@ -93,12 +93,8 @@ class BootArchiveSupport {
attributes.putIfAbsent("Main-Class", this.loaderMainClass);
attributes.putIfAbsent("Start-Class", mainClass);
attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion());
if (classes != null) {
attributes.putIfAbsent("Spring-Boot-Classes", classes);
}
if (lib != null) {
attributes.putIfAbsent("Spring-Boot-Lib", lib);
}
attributes.putIfAbsent("Spring-Boot-Classes", classes);
attributes.putIfAbsent("Spring-Boot-Lib", lib);
if (classPathIndex != null) {
attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex);
}
......@@ -113,10 +109,10 @@ class BootArchiveSupport {
}
CopyAction createCopyAction(Jar jar) {
return createCopyAction(jar, null, false);
return createCopyAction(jar, null, null);
}
CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, boolean includeLayerTools) {
CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, String layerToolsLocation) {
File output = jar.getArchiveFile().get().getAsFile();
Manifest manifest = jar.getManifest();
boolean preserveFileTimestamps = jar.isPreserveFileTimestamps();
......@@ -128,8 +124,8 @@ class BootArchiveSupport {
Function<FileCopyDetails, ZipCompression> compressionResolver = this.compressionResolver;
String encoding = jar.getMetadataCharset();
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, includeDefaultLoader,
includeLayerTools, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver, encoding,
layerResolver);
layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver,
encoding, layerResolver);
return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action;
}
......
......@@ -97,14 +97,8 @@ public class BootJar extends Jar implements BootArchive {
@Override
public void copy() {
if (this.layered != null) {
this.support.configureManifest(getManifest(), getMainClassName(), null, null, CLASSPATH_INDEX,
LAYERS_INDEX);
}
else {
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER,
CLASSPATH_INDEX, null);
}
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER, CLASSPATH_INDEX,
(this.layered != null) ? LAYERS_INDEX : null);
super.copy();
}
......@@ -112,8 +106,8 @@ public class BootJar extends Jar implements BootArchive {
protected CopyAction createCopyAction() {
if (this.layered != null) {
LayerResolver layerResolver = new LayerResolver(getConfigurations(), this.layered, this::isLibrary);
boolean includeLayerTools = this.layered.isIncludeLayerTools();
return this.support.createCopyAction(this, layerResolver, includeLayerTools);
String layerToolsLocation = this.layered.isIncludeLayerTools() ? LIB_FOLDER : null;
return this.support.createCopyAction(this, layerResolver, layerToolsLocation);
}
return this.support.createCopyAction(this);
}
......
......@@ -18,9 +18,7 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.Configuration;
......@@ -30,9 +28,7 @@ import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.file.FileCopyDetails;
import org.gradle.api.specs.Spec;
import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.Layers;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCoordinates;
......@@ -47,8 +43,6 @@ import org.springframework.boot.loader.tools.LibraryCoordinates;
*/
class LayerResolver {
private static final String BOOT_INF_FOLDER = "BOOT-INF/";
private final ResolvedDependencies resolvedDependencies;
private final LayeredSpec layeredConfiguration;
......@@ -62,40 +56,28 @@ class LayerResolver {
this.librarySpec = librarySpec;
}
String getPath(JarModeLibrary jarModeLibrary) {
Layers layers = this.layeredConfiguration.asLayers();
Layer layer = layers.getLayer(jarModeLibrary);
if (layer != null) {
return BOOT_INF_FOLDER + "layers/" + layer + "/lib/" + jarModeLibrary.getName();
}
return BOOT_INF_FOLDER + "lib/" + jarModeLibrary.getName();
}
String getPath(FileCopyDetails details) {
String path = details.getRelativePath().getPathString();
Layer layer = getLayer(details);
if (layer == null || !path.startsWith(BOOT_INF_FOLDER)) {
return path;
}
path = path.substring(BOOT_INF_FOLDER.length());
return BOOT_INF_FOLDER + "layers/" + layer + "/" + path;
}
Layer getLayer(FileCopyDetails details) {
Layers layers = this.layeredConfiguration.asLayers();
try {
if (this.librarySpec.isSatisfiedBy(details)) {
return layers.getLayer(asLibrary(details));
return getLayer(asLibrary(details));
}
return layers.getLayer(details.getSourcePath());
return getLayer(details.getSourcePath());
}
catch (UnsupportedOperationException ex) {
return null;
}
}
List<String> getLayerNames() {
return this.layeredConfiguration.asLayers().stream().map(Layer::toString).collect(Collectors.toList());
Layer getLayer(Library library) {
return this.layeredConfiguration.asLayers().getLayer(library);
}
Layer getLayer(String applicationResource) {
return this.layeredConfiguration.asLayers().getLayer(applicationResource);
}
Iterable<Layer> getLayers() {
return this.layeredConfiguration.asLayers();
}
private Library asLibrary(FileCopyDetails details) {
......
......@@ -19,7 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
......@@ -28,7 +28,6 @@ import org.apache.commons.compress.archivers.zip.UnixStat;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.gradle.api.file.FileTreeElement;
import org.gradle.api.specs.Spec;
/**
* Internal utility used to copy entries from the {@code spring-boot-loader.jar}.
......@@ -38,29 +37,30 @@ import org.gradle.api.specs.Spec;
*/
class LoaderZipEntries {
private Long entryTime;
private final Long entryTime;
LoaderZipEntries(Long entryTime) {
this.entryTime = entryTime;
}
Spec<FileTreeElement> writeTo(ZipArchiveOutputStream out) throws IOException {
WrittenDirectoriesSpec writtenDirectoriesSpec = new WrittenDirectoriesSpec();
WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException {
WrittenEntries written = new WrittenEntries();
try (ZipInputStream loaderJar = new ZipInputStream(
getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
java.util.zip.ZipEntry entry = loaderJar.getNextEntry();
while (entry != null) {
if (entry.isDirectory() && !entry.getName().equals("META-INF/")) {
writeDirectory(new ZipArchiveEntry(entry), out);
writtenDirectoriesSpec.add(entry);
written.addDirectory(entry);
}
else if (entry.getName().endsWith(".class")) {
writeClass(new ZipArchiveEntry(entry), loaderJar, out);
written.addFile(entry);
}
entry = loaderJar.getNextEntry();
}
}
return writtenDirectoriesSpec;
return written;
}
private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException {
......@@ -92,23 +92,32 @@ class LoaderZipEntries {
}
/**
* Spec to track directories that have been written.
* Tracks entries that have been written.
*/
private static class WrittenDirectoriesSpec implements Spec<FileTreeElement> {
static class WrittenEntries {
private final Set<String> entries = new HashSet<>();
private final Set<String> directories = new LinkedHashSet<>();
@Override
public boolean isSatisfiedBy(FileTreeElement element) {
private final Set<String> files = new LinkedHashSet<>();
private void addDirectory(ZipEntry entry) {
this.directories.add(entry.getName());
}
private void addFile(ZipEntry entry) {
this.files.add(entry.getName());
}
boolean isWrittenDirectory(FileTreeElement element) {
String path = element.getRelativePath().getPathString();
if (element.isDirectory() && !path.endsWith(("/"))) {
path += "/";
}
return this.entries.contains(path);
return this.directories.contains(path);
}
void add(ZipEntry entry) {
this.entries.add(entry.getName());
Set<String> getFiles() {
return this.files;
}
}
......
......@@ -437,12 +437,16 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
}
protected List<String> getEntryNames(File file) throws IOException {
List<String> entryNames = new ArrayList<>();
try (JarFile jarFile = new JarFile(file)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
entryNames.add(entries.nextElement().getName());
}
return getEntryNames(jarFile);
}
}
protected List<String> getEntryNames(JarFile jarFile) {
List<String> entryNames = new ArrayList<>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
entryNames.add(entries.nextElement().getName());
}
return entryNames;
}
......
......@@ -16,14 +16,25 @@
package org.springframework.boot.gradle.tasks.bundling;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
......@@ -77,14 +88,45 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
writeMainClass();
writeResource();
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(jarModeLayerTools())).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/commons-lang3-3.9.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/commons-io-2.7-SNAPSHOT.jar"))
.isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/application/classes/example/Main.class")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/application/classes/static/file.txt")).isNotNull();
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-io-2.7-SNAPSHOT.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
indexedLayers = readLayerIndex(jarFile);
}
List<String> layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies",
"application");
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
List<String> expectedDependencies = new ArrayList<>();
expectedDependencies.add("BOOT-INF/lib/commons-lang3-3.9.jar");
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
List<String> expectedSnapshotDependencies = new ArrayList<>();
expectedSnapshotDependencies.add("BOOT-INF/lib/commons-io-2.7-SNAPSHOT.jar");
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
assertThat(indexedLayers.get("spring-boot-loader"))
.allMatch(Pattern.compile("org/springframework/boot/loader/.+\\.class").asPredicate());
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
assertThat(indexedLayers.get("application")).containsExactly("META-INF/MANIFEST.MF",
"BOOT-INF/classes/example/Main.class", "BOOT-INF/classes/static/file.txt", "BOOT-INF/classpath.idx",
"BOOT-INF/layers.idx");
BuildResult listLayers = this.gradleBuild.build("listLayers");
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
String listLayersOutput = listLayers.getOutput();
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames);
assertThat(extractedLayers.keySet()).isEqualTo(indexedLayers.keySet());
extractedLayers.forEach(
(name, contents) -> assertThat(contents).containsExactlyInAnyOrderElementsOf(indexedLayers.get(name)));
}
@TestTemplate
......@@ -92,23 +134,49 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
writeMainClass();
writeResource();
BuildResult build = this.gradleBuild.build("bootJar");
System.out.println(build.getOutput());
assertThat(build.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> indexedLayers;
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
assertThat(jarFile.getEntry(jarModeLayerTools())).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/commons-dependencies/lib/commons-lang3-3.9.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/commons-io-2.7-SNAPSHOT.jar"))
.isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/app/classes/example/Main.class")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers/static/classes/static/file.txt")).isNotNull();
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-io-2.7-SNAPSHOT.jar")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
assertThat(jarFile.getEntry("BOOT-INF/layers.idx")).isNotNull();
indexedLayers = readLayerIndex(jarFile);
}
}
private String jarModeLayerTools() {
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
String version = library.getCoordinates().getVersion();
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
List<String> layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies",
"static", "app");
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
List<String> expectedDependencies = new ArrayList<>();
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
List<String> expectedSnapshotDependencies = new ArrayList<>();
expectedSnapshotDependencies.add("BOOT-INF/lib/commons-io-2.7-SNAPSHOT.jar");
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
assertThat(indexedLayers.get("commons-dependencies")).containsExactly("BOOT-INF/lib/commons-lang3-3.9.jar");
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
assertThat(indexedLayers.get("static")).containsExactly("BOOT-INF/classes/static/file.txt");
List<String> appLayer = new ArrayList<>(indexedLayers.get("app"));
List<String> nonLoaderEntries = Arrays.asList("META-INF/MANIFEST.MF", "BOOT-INF/classes/example/Main.class",
"BOOT-INF/classpath.idx", "BOOT-INF/layers.idx");
assertThat(appLayer).containsSubsequence(nonLoaderEntries);
appLayer.removeAll(nonLoaderEntries);
assertThat(appLayer).allMatch(Pattern.compile("org/springframework/boot/loader/.+\\.class").asPredicate());
BuildResult listLayers = this.gradleBuild.build("listLayers");
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
String listLayersOutput = listLayers.getOutput();
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames);
assertThat(extractedLayers.keySet()).isEqualTo(indexedLayers.keySet());
extractedLayers.forEach(
(name, contents) -> assertThat(contents).containsExactlyInAnyOrderElementsOf(indexedLayers.get(name)));
}
private void writeMainClass() {
......@@ -144,4 +212,24 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
}
}
private Map<String, List<String>> readLayerIndex(JarFile jarFile) throws IOException {
ZipEntry indexEntry = jarFile.getEntry("BOOT-INF/layers.idx");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
return reader.lines().map((line) -> line.split(" "))
.collect(Collectors.groupingBy((layerAndPath) -> layerAndPath[0], LinkedHashMap::new,
Collectors.mapping((layerAndPath) -> layerAndPath[1], Collectors.toList())));
}
}
private Map<String, List<String>> readExtractedLayers(File root, List<String> layerNames) throws IOException {
Map<String, List<String>> extractedLayers = new LinkedHashMap<>();
for (String layerName : layerNames) {
File layer = new File(root, layerName);
assertThat(layer).isDirectory();
extractedLayers.put(layerName, Files.walk(layer.toPath()).filter((path) -> path.toFile().isFile())
.map(layer.toPath()::relativize).map(Path::toString).collect(Collectors.toList()));
}
return extractedLayers;
}
}
......@@ -79,46 +79,41 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
}
}
@Test
void whenJarIsLayeredThenBootInfContainsOnlyLayersAndIndexFiles() throws IOException {
List<String> entryNames = getEntryNames(createLayeredJar());
assertThat(entryNames.stream().filter((name) -> name.startsWith("BOOT-INF/"))
.filter((name) -> !name.startsWith("BOOT-INF/layers/"))).contains("BOOT-INF/layers.idx",
"BOOT-INF/classpath.idx");
}
@Test
void whenJarIsLayeredThenManifestContainsEntryForLayersIndexInPlaceOfClassesAndLib() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar())) {
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes")).isEqualTo(null);
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(null);
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");
}
}
@Test
void whenJarIsLayeredThenLayersIndexIsPresentAndListsLayersInOrder() throws IOException {
void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar())) {
assertThat(entryLines(jarFile, "BOOT-INF/layers.idx")).containsExactly("dependencies", "spring-boot-loader",
List<String> entryNames = getEntryNames(jarFile);
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/classes/com/example/Application.class",
"BOOT-INF/classes/application.properties", "BOOT-INF/classes/static/test.css");
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader",
"snapshot-dependencies", "application");
assertThat(index).contains("dependencies BOOT-INF/lib/first-library.jar",
"dependencies BOOT-INF/lib/second-library.jar",
"snapshot-dependencies BOOT-INF/lib/third-library-SNAPSHOT.jar",
"application BOOT-INF/classes/com/example/Application.class",
"application BOOT-INF/classes/application.properties",
"application BOOT-INF/classes/static/test.css");
}
}
@Test
void whenJarIsLayeredThenContentsAreMovedToLayerDirectories() throws IOException {
List<String> entryNames = getEntryNames(createLayeredJar());
assertThat(entryNames)
.containsSubsequence("BOOT-INF/layers/dependencies/lib/first-library.jar",
"BOOT-INF/layers/dependencies/lib/second-library.jar")
.contains("BOOT-INF/layers/snapshot-dependencies/lib/third-library-SNAPSHOT.jar")
.containsSubsequence("BOOT-INF/layers/application/classes/com/example/Application.class",
"BOOT-INF/layers/application/classes/application.properties")
.contains("BOOT-INF/layers/application/classes/static/test.css");
}
@Test
void whenJarIsLayeredWithCustomStrategiesThenContentsAreMovedToLayerDirectories() throws IOException {
void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrent() throws IOException {
File jar = createLayeredJar((layered) -> {
layered.application((application) -> {
application.intoLayer("resources", (spec) -> spec.include("static/**"));
......@@ -131,25 +126,30 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
});
layered.layerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
});
List<String> entryNames = getEntryNames(jar);
assertThat(entryNames)
.containsSubsequence("BOOT-INF/layers/my-internal-deps/lib/first-library.jar",
"BOOT-INF/layers/my-internal-deps/lib/second-library.jar")
.contains("BOOT-INF/layers/my-snapshot-deps/lib/third-library-SNAPSHOT.jar")
.containsSubsequence("BOOT-INF/layers/application/classes/com/example/Application.class",
"BOOT-INF/layers/application/classes/application.properties")
.contains("BOOT-INF/layers/resources/classes/static/test.css");
try (JarFile jarFile = new JarFile(jar)) {
List<String> entryNames = getEntryNames(jar);
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/classes/com/example/Application.class",
"BOOT-INF/classes/application.properties", "BOOT-INF/classes/static/test.css");
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps",
"resources", "application");
assertThat(index).contains("my-internal-deps BOOT-INF/lib/first-library.jar",
"my-internal-deps BOOT-INF/lib/second-library.jar",
"my-snapshot-deps BOOT-INF/lib/third-library-SNAPSHOT.jar",
"application BOOT-INF/classes/com/example/Application.class",
"application BOOT-INF/classes/application.properties",
"resources BOOT-INF/classes/static/test.css");
}
}
@Test
void whenJarIsLayeredJarsInLibAreStored() throws IOException {
void jarsInLibAreStored() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar())) {
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/first-library.jar").getMethod())
.isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/second-library.jar").getMethod())
assertThat(jarFile.getEntry("BOOT-INF/lib/first-library.jar").getMethod()).isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/lib/second-library.jar").getMethod()).isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/lib/third-library-SNAPSHOT.jar").getMethod())
.isEqualTo(ZipEntry.STORED);
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/third-library-SNAPSHOT.jar")
.getMethod()).isEqualTo(ZipEntry.STORED);
}
}
......@@ -164,14 +164,7 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
@Test
void whenJarIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException {
List<String> entryNames = getEntryNames(createLayeredJar());
assertThat(entryNames).contains(jarModeLayerTools());
}
private String jarModeLayerTools() {
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
String version = library.getCoordinates().getVersion();
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
assertThat(entryNames).contains("BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName());
}
@Test
......@@ -267,6 +260,14 @@ class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
}
}
private Set<String> getLayerNames(List<String> index) {
return index.stream().map(this::getLayerName).collect(Collectors.toCollection(LinkedHashSet::new));
}
private String getLayerName(String indexLine) {
return indexLine.substring(0, indexLine.indexOf(" "));
}
@Override
protected void executeTask() {
getTask().copy();
......
......@@ -31,6 +31,19 @@ repositories {
}
dependencies {
implementation("org.apache.commons:commons-lang3:3.9")
implementation("commons-io:commons-io:2.7-SNAPSHOT")
implementation("org.apache.commons:commons-lang3:3.9")
implementation("org.springframework:spring-core:5.2.5.RELEASE")
}
task listLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "layertools" ]
args "list"
}
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "layertools" ]
args "extract"
}
......@@ -14,6 +14,19 @@ repositories {
}
dependencies {
implementation("org.apache.commons:commons-lang3:3.9")
implementation("commons-io:commons-io:2.7-SNAPSHOT")
implementation("org.apache.commons:commons-lang3:3.9")
implementation("org.springframework:spring-core:5.2.5.RELEASE")
}
task listLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "layertools" ]
args "list"
}
task extractLayers(type: JavaExec) {
classpath = bootJar.outputs.files
systemProperties = [ "jarmode": "layertools" ]
args "extract"
}
\ No newline at end of file
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