From bfa04e6574b642eca0d3adfd7d3c3ce8e4d26313 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 2 Apr 2020 22:44:56 -0700 Subject: [PATCH] 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: Phillip Webb --- .../tasks/bundling/BootArchiveSupport.java | 16 +- .../boot/gradle/tasks/bundling/BootJar.java | 14 +- .../tasks/bundling/BootZipCopyAction.java | 144 ++++++++++++------ .../gradle/tasks/bundling/LayerResolver.java | 42 ++--- .../tasks/bundling/LoaderZipEntries.java | 39 +++-- .../bundling/AbstractBootArchiveTests.java | 14 +- .../bundling/BootJarIntegrationTests.java | 128 +++++++++++++--- .../gradle/tasks/bundling/BootJarTests.java | 95 ++++++------ ...ootJarIntegrationTests-customLayers.gradle | 15 +- ...tJarIntegrationTests-implicitLayers.gradle | 15 +- 10 files changed, 334 insertions(+), 188 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 5cda8cf720f..a2cce86f048 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -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 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; } 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 2ea8349fb3e..d6dd6ae6d13 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 @@ -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); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 0267e6f81bd..10eb611628b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -48,7 +48,12 @@ import org.gradle.api.tasks.WorkResults; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.FileUtils; import org.springframework.boot.loader.tools.JarModeLibrary; +import org.springframework.boot.loader.tools.Layer; +import org.springframework.boot.loader.tools.LayersIndex; +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; /** * A {@link CopyAction} for creating a Spring Boot zip archive (typically a jar or war). @@ -70,7 +75,7 @@ class BootZipCopyAction implements CopyAction { private final boolean includeDefaultLoader; - private final boolean includeLayerTools; + private final String layerToolsLocation; private final Spec requiresUnpack; @@ -87,7 +92,7 @@ class BootZipCopyAction implements CopyAction { private final LayerResolver layerResolver; BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, boolean includeDefaultLoader, - boolean includeLayerTools, Spec requiresUnpack, Spec exclusions, + String layerToolsLocation, Spec requiresUnpack, Spec exclusions, LaunchScriptConfiguration launchScript, Spec librarySpec, Function compressionResolver, String encoding, LayerResolver layerResolver) { @@ -95,7 +100,7 @@ class BootZipCopyAction implements CopyAction { this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; this.includeDefaultLoader = includeDefaultLoader; - this.includeLayerTools = includeLayerTools; + this.layerToolsLocation = layerToolsLocation; this.requiresUnpack = requiresUnpack; this.exclusions = exclusions; this.launchScript = launchScript; @@ -177,7 +182,9 @@ class BootZipCopyAction implements CopyAction { private ZipArchiveOutputStream out; - private Spec writtenLoaderEntries; + private final LayersIndex layerIndex; + + private LoaderZipEntries.WrittenEntries writtenLoaderEntries; private Set writtenDirectories = new LinkedHashSet<>(); @@ -185,6 +192,8 @@ class BootZipCopyAction implements CopyAction { Processor(ZipArchiveOutputStream out) { this.out = out; + this.layerIndex = (BootZipCopyAction.this.layerResolver != null) + ? new LayersIndex(BootZipCopyAction.this.layerResolver.getLayers()) : null; } void process(FileCopyDetails details) { @@ -207,11 +216,11 @@ class BootZipCopyAction implements CopyAction { private boolean skipProcessing(FileCopyDetails details) { return BootZipCopyAction.this.exclusions.isSatisfiedBy(details) - || (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details)); + || (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isWrittenDirectory(details)); } private void processDirectory(FileCopyDetails details) throws IOException { - String name = getEntryName(details); + String name = details.getRelativePath().getPathString(); long time = getTime(details); writeParentDirectoriesIfNecessary(name, time); ZipArchiveEntry entry = new ZipArchiveEntry(name + '/'); @@ -223,7 +232,7 @@ class BootZipCopyAction implements CopyAction { } private void processFile(FileCopyDetails details) throws IOException { - String name = getEntryName(details); + String name = details.getRelativePath().getPathString(); long time = getTime(details); writeParentDirectoriesIfNecessary(name, time); ZipArchiveEntry entry = new ZipArchiveEntry(name); @@ -239,6 +248,10 @@ class BootZipCopyAction implements CopyAction { if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) { this.writtenLibraries.add(name.substring(name.lastIndexOf('/') + 1)); } + if (BootZipCopyAction.this.layerResolver != null) { + Layer layer = BootZipCopyAction.this.layerResolver.getLayer(details); + this.layerIndex.add(layer, name); + } } private void writeParentDirectoriesIfNecessary(String name, long time) throws IOException { @@ -261,34 +274,12 @@ class BootZipCopyAction implements CopyAction { return name.substring(0, lastSlash); } - private String getEntryName(FileCopyDetails details) { - if (BootZipCopyAction.this.layerResolver == null) { - return details.getRelativePath().getPathString(); - } - return BootZipCopyAction.this.layerResolver.getPath(details); - } - - private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException { - archiveEntry.setMethod(java.util.zip.ZipEntry.STORED); - archiveEntry.setSize(details.getSize()); - archiveEntry.setCompressedSize(details.getSize()); - archiveEntry.setCrc(getCrc(details)); - if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) { - archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile())); - } - } - - private long getCrc(FileCopyDetails details) { - Crc32OutputStream crcStream = new Crc32OutputStream(); - details.copyTo(crcStream); - return crcStream.getCrc(); - } - void finish() throws IOException { writeLoaderEntriesIfNecessary(null); writeJarToolsIfNecessary(); - writeLayersIndexIfNecessary(); writeClassPathIndexIfNecessary(); + // We must write the layer index last + writeLayersIndexIfNecessary(); } private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOException { @@ -299,9 +290,15 @@ class BootZipCopyAction implements CopyAction { // Don't write loader entries until after META-INF folder (see gh-16698) return; } - LoaderZipEntries entries = new LoaderZipEntries( + LoaderZipEntries loaderEntries = new LoaderZipEntries( BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES); - this.writtenLoaderEntries = entries.writeTo(this.out); + this.writtenLoaderEntries = loaderEntries.writeTo(this.out); + if (BootZipCopyAction.this.layerResolver != null) { + for (String name : this.writtenLoaderEntries.getFiles()) { + Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name); + this.layerIndex.add(layer, name); + } + } } private boolean isInMetaInf(FileCopyDetails details) { @@ -313,44 +310,75 @@ class BootZipCopyAction implements CopyAction { } private void writeJarToolsIfNecessary() throws IOException { - if (BootZipCopyAction.this.layerResolver == null || !BootZipCopyAction.this.includeLayerTools) { - return; + if (BootZipCopyAction.this.layerToolsLocation != null) { + writeJarModeLibrary(BootZipCopyAction.this.layerToolsLocation, JarModeLibrary.LAYER_TOOLS); } - writeJarModeLibrary(JarModeLibrary.LAYER_TOOLS); } - private void writeJarModeLibrary(JarModeLibrary jarModeLibrary) throws IOException { - String name = BootZipCopyAction.this.layerResolver.getPath(jarModeLibrary); - writeFile(name, ZipEntryWriter.fromInputStream(jarModeLibrary.openStream())); + private void writeJarModeLibrary(String location, JarModeLibrary library) throws IOException { + String name = location + library.getName(); + writeEntry(name, ZipEntryWriter.fromInputStream(library.openStream()), false, + (entry) -> prepareStoredEntry(library.openStream(), entry)); + if (BootZipCopyAction.this.layerResolver != null) { + Layer layer = BootZipCopyAction.this.layerResolver.getLayer(library); + this.layerIndex.add(layer, name); + } } - private void writeLayersIndexIfNecessary() throws IOException { - Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); - String layersIndex = (String) manifestAttributes.get("Spring-Boot-Layers-Index"); - if (layersIndex != null && BootZipCopyAction.this.layerResolver != null) { - writeFile(layersIndex, ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, - BootZipCopyAction.this.layerResolver.getLayerNames())); + private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException { + prepareStoredEntry(details.open(), archiveEntry); + if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) { + archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile())); } } + private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException { + archiveEntry.setMethod(java.util.zip.ZipEntry.STORED); + Crc32OutputStream crcStream = new Crc32OutputStream(); + int size = FileCopyUtils.copy(input, crcStream); + archiveEntry.setSize(size); + archiveEntry.setCompressedSize(size); + archiveEntry.setCrc(crcStream.getCrc()); + } + private void writeClassPathIndexIfNecessary() throws IOException { Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index"); if (classPathIndex != null) { - writeFile(classPathIndex, - ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, this.writtenLibraries)); + writeEntry(classPathIndex, + ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, this.writtenLibraries), true); + } + } + + private void writeLayersIndexIfNecessary() throws IOException { + if (BootZipCopyAction.this.layerResolver != null) { + Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes(); + String name = (String) manifestAttributes.get("Spring-Boot-Layers-Index"); + Assert.state(StringUtils.hasText(name), "Missing layer index manifest attribute"); + Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name); + this.layerIndex.add(layer, name); + writeEntry(name, (entry, out) -> this.layerIndex.writeTo(out), false); } } - private void writeFile(String name, ZipEntryWriter entryWriter) throws IOException { + private void writeEntry(String name, ZipEntryWriter entryWriter, boolean addToLayerIndex) throws IOException { + writeEntry(name, entryWriter, addToLayerIndex, ZipEntryCustomizer.NONE); + } + + private void writeEntry(String name, ZipEntryWriter entryWriter, boolean addToLayerIndex, + ZipEntryCustomizer entryCustomizer) throws IOException { writeParentDirectoriesIfNecessary(name, CONSTANT_TIME_FOR_ZIP_ENTRIES); ZipArchiveEntry entry = new ZipArchiveEntry(name); entry.setUnixMode(UnixStat.FILE_FLAG); entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES); + entryCustomizer.customize(entry); this.out.putArchiveEntry(entry); entryWriter.writeTo(entry, this.out); this.out.closeArchiveEntry(); - + if (addToLayerIndex && BootZipCopyAction.this.layerResolver != null) { + Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name); + this.layerIndex.add(layer, name); + } } private long getTime(FileCopyDetails details) { @@ -360,6 +388,24 @@ class BootZipCopyAction implements CopyAction { } + /** + * Callback interface used to customize a {@link ZipArchiveEntry}. + */ + @FunctionalInterface + private interface ZipEntryCustomizer { + + ZipEntryCustomizer NONE = (entry) -> { + }; + + /** + * Customize the entry. + * @param entry the entry to customize + * @throws IOException on IO error + */ + void customize(ZipArchiveEntry entry) throws IOException; + + } + /** * Callback used to write a zip entry data. */ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java index cd6cf66b993..fad76ac3647 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LayerResolver.java @@ -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 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 getLayers() { + return this.layeredConfiguration.asLayers(); } private Library asLibrary(FileCopyDetails details) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java index a6fcd39612d..b7caad06386 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java @@ -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 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 { + static class WrittenEntries { - private final Set entries = new HashSet<>(); + private final Set directories = new LinkedHashSet<>(); - @Override - public boolean isSatisfiedBy(FileTreeElement element) { + private final Set 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 getFiles() { + return this.files; } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index ccc150c8ca2..527d2becf47 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -437,12 +437,16 @@ abstract class AbstractBootArchiveTests { } protected List getEntryNames(File file) throws IOException { - List entryNames = new ArrayList<>(); try (JarFile jarFile = new JarFile(file)) { - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - entryNames.add(entries.nextElement().getName()); - } + return getEntryNames(jarFile); + } + } + + protected List getEntryNames(JarFile jarFile) { + List entryNames = new ArrayList<>(); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + entryNames.add(entries.nextElement().getName()); } return entryNames; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java index a8e7b6eeeb4..66923903fc3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java @@ -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> 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 layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies", + "application"); + assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames); + List 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 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> 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> 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 layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies", + "static", "app"); + assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames); + List 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 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 appLayer = new ArrayList<>(indexedLayers.get("app")); + List 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> 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> 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> readExtractedLayers(File root, List layerNames) throws IOException { + Map> 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; + } + } 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 710ab6a0a4d..058d3b1d9a3 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 @@ -79,46 +79,41 @@ class BootJarTests extends AbstractBootArchiveTests { } } - @Test - void whenJarIsLayeredThenBootInfContainsOnlyLayersAndIndexFiles() throws IOException { - List 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 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 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 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 { }); layered.layerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application"); }); - List 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 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 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 { @Test void whenJarIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException { List 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 { } } + private Set getLayerNames(List 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(); 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 f3bec81b597..15cce1e6393 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 @@ -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" } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle index 1cd9de76bf4..22dc55ae655 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-implicitLayers.gradle @@ -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