Skip to content

Commit bfa04e6

Browse files
wilkinsonaphilwebb
andcommitted
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 <[email protected]>
1 parent 4e3cdf9 commit bfa04e6

File tree

10 files changed

+334
-188
lines changed

10 files changed

+334
-188
lines changed

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,8 @@ void configureManifest(Manifest manifest, String mainClass, String classes, Stri
9393
attributes.putIfAbsent("Main-Class", this.loaderMainClass);
9494
attributes.putIfAbsent("Start-Class", mainClass);
9595
attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion());
96-
if (classes != null) {
97-
attributes.putIfAbsent("Spring-Boot-Classes", classes);
98-
}
99-
if (lib != null) {
100-
attributes.putIfAbsent("Spring-Boot-Lib", lib);
101-
}
96+
attributes.putIfAbsent("Spring-Boot-Classes", classes);
97+
attributes.putIfAbsent("Spring-Boot-Lib", lib);
10298
if (classPathIndex != null) {
10399
attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex);
104100
}
@@ -113,10 +109,10 @@ private String determineSpringBootVersion() {
113109
}
114110

115111
CopyAction createCopyAction(Jar jar) {
116-
return createCopyAction(jar, null, false);
112+
return createCopyAction(jar, null, null);
117113
}
118114

119-
CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, boolean includeLayerTools) {
115+
CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, String layerToolsLocation) {
120116
File output = jar.getArchiveFile().get().getAsFile();
121117
Manifest manifest = jar.getManifest();
122118
boolean preserveFileTimestamps = jar.isPreserveFileTimestamps();
@@ -128,8 +124,8 @@ CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, boolean includ
128124
Function<FileCopyDetails, ZipCompression> compressionResolver = this.compressionResolver;
129125
String encoding = jar.getMetadataCharset();
130126
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, includeDefaultLoader,
131-
includeLayerTools, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver, encoding,
132-
layerResolver);
127+
layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver,
128+
encoding, layerResolver);
133129
return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action;
134130
}
135131

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,23 +97,17 @@ private Iterable<File> classpathEntries(Spec<File> filter) {
9797

9898
@Override
9999
public void copy() {
100-
if (this.layered != null) {
101-
this.support.configureManifest(getManifest(), getMainClassName(), null, null, CLASSPATH_INDEX,
102-
LAYERS_INDEX);
103-
}
104-
else {
105-
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER,
106-
CLASSPATH_INDEX, null);
107-
}
100+
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER, CLASSPATH_INDEX,
101+
(this.layered != null) ? LAYERS_INDEX : null);
108102
super.copy();
109103
}
110104

111105
@Override
112106
protected CopyAction createCopyAction() {
113107
if (this.layered != null) {
114108
LayerResolver layerResolver = new LayerResolver(getConfigurations(), this.layered, this::isLibrary);
115-
boolean includeLayerTools = this.layered.isIncludeLayerTools();
116-
return this.support.createCopyAction(this, layerResolver, includeLayerTools);
109+
String layerToolsLocation = this.layered.isIncludeLayerTools() ? LIB_FOLDER : null;
110+
return this.support.createCopyAction(this, layerResolver, layerToolsLocation);
117111
}
118112
return this.support.createCopyAction(this);
119113
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java

Lines changed: 95 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848
import org.springframework.boot.loader.tools.DefaultLaunchScript;
4949
import org.springframework.boot.loader.tools.FileUtils;
5050
import org.springframework.boot.loader.tools.JarModeLibrary;
51+
import org.springframework.boot.loader.tools.Layer;
52+
import org.springframework.boot.loader.tools.LayersIndex;
53+
import org.springframework.util.Assert;
54+
import org.springframework.util.FileCopyUtils;
5155
import org.springframework.util.StreamUtils;
56+
import org.springframework.util.StringUtils;
5257

5358
/**
5459
* A {@link CopyAction} for creating a Spring Boot zip archive (typically a jar or war).
@@ -70,7 +75,7 @@ class BootZipCopyAction implements CopyAction {
7075

7176
private final boolean includeDefaultLoader;
7277

73-
private final boolean includeLayerTools;
78+
private final String layerToolsLocation;
7479

7580
private final Spec<FileTreeElement> requiresUnpack;
7681

@@ -87,15 +92,15 @@ class BootZipCopyAction implements CopyAction {
8792
private final LayerResolver layerResolver;
8893

8994
BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, boolean includeDefaultLoader,
90-
boolean includeLayerTools, Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
95+
String layerToolsLocation, Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
9196
LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
9297
Function<FileCopyDetails, ZipCompression> compressionResolver, String encoding,
9398
LayerResolver layerResolver) {
9499
this.output = output;
95100
this.manifest = manifest;
96101
this.preserveFileTimestamps = preserveFileTimestamps;
97102
this.includeDefaultLoader = includeDefaultLoader;
98-
this.includeLayerTools = includeLayerTools;
103+
this.layerToolsLocation = layerToolsLocation;
99104
this.requiresUnpack = requiresUnpack;
100105
this.exclusions = exclusions;
101106
this.launchScript = launchScript;
@@ -177,14 +182,18 @@ private class Processor {
177182

178183
private ZipArchiveOutputStream out;
179184

180-
private Spec<FileTreeElement> writtenLoaderEntries;
185+
private final LayersIndex layerIndex;
186+
187+
private LoaderZipEntries.WrittenEntries writtenLoaderEntries;
181188

182189
private Set<String> writtenDirectories = new LinkedHashSet<>();
183190

184191
private Set<String> writtenLibraries = new LinkedHashSet<>();
185192

186193
Processor(ZipArchiveOutputStream out) {
187194
this.out = out;
195+
this.layerIndex = (BootZipCopyAction.this.layerResolver != null)
196+
? new LayersIndex(BootZipCopyAction.this.layerResolver.getLayers()) : null;
188197
}
189198

190199
void process(FileCopyDetails details) {
@@ -207,11 +216,11 @@ void process(FileCopyDetails details) {
207216

208217
private boolean skipProcessing(FileCopyDetails details) {
209218
return BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
210-
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details));
219+
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isWrittenDirectory(details));
211220
}
212221

213222
private void processDirectory(FileCopyDetails details) throws IOException {
214-
String name = getEntryName(details);
223+
String name = details.getRelativePath().getPathString();
215224
long time = getTime(details);
216225
writeParentDirectoriesIfNecessary(name, time);
217226
ZipArchiveEntry entry = new ZipArchiveEntry(name + '/');
@@ -223,7 +232,7 @@ private void processDirectory(FileCopyDetails details) throws IOException {
223232
}
224233

225234
private void processFile(FileCopyDetails details) throws IOException {
226-
String name = getEntryName(details);
235+
String name = details.getRelativePath().getPathString();
227236
long time = getTime(details);
228237
writeParentDirectoriesIfNecessary(name, time);
229238
ZipArchiveEntry entry = new ZipArchiveEntry(name);
@@ -239,6 +248,10 @@ private void processFile(FileCopyDetails details) throws IOException {
239248
if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) {
240249
this.writtenLibraries.add(name.substring(name.lastIndexOf('/') + 1));
241250
}
251+
if (BootZipCopyAction.this.layerResolver != null) {
252+
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(details);
253+
this.layerIndex.add(layer, name);
254+
}
242255
}
243256

244257
private void writeParentDirectoriesIfNecessary(String name, long time) throws IOException {
@@ -261,34 +274,12 @@ private String getParentDirectory(String name) {
261274
return name.substring(0, lastSlash);
262275
}
263276

264-
private String getEntryName(FileCopyDetails details) {
265-
if (BootZipCopyAction.this.layerResolver == null) {
266-
return details.getRelativePath().getPathString();
267-
}
268-
return BootZipCopyAction.this.layerResolver.getPath(details);
269-
}
270-
271-
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
272-
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
273-
archiveEntry.setSize(details.getSize());
274-
archiveEntry.setCompressedSize(details.getSize());
275-
archiveEntry.setCrc(getCrc(details));
276-
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
277-
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
278-
}
279-
}
280-
281-
private long getCrc(FileCopyDetails details) {
282-
Crc32OutputStream crcStream = new Crc32OutputStream();
283-
details.copyTo(crcStream);
284-
return crcStream.getCrc();
285-
}
286-
287277
void finish() throws IOException {
288278
writeLoaderEntriesIfNecessary(null);
289279
writeJarToolsIfNecessary();
290-
writeLayersIndexIfNecessary();
291280
writeClassPathIndexIfNecessary();
281+
// We must write the layer index last
282+
writeLayersIndexIfNecessary();
292283
}
293284

294285
private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOException {
@@ -299,9 +290,15 @@ private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOExc
299290
// Don't write loader entries until after META-INF folder (see gh-16698)
300291
return;
301292
}
302-
LoaderZipEntries entries = new LoaderZipEntries(
293+
LoaderZipEntries loaderEntries = new LoaderZipEntries(
303294
BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES);
304-
this.writtenLoaderEntries = entries.writeTo(this.out);
295+
this.writtenLoaderEntries = loaderEntries.writeTo(this.out);
296+
if (BootZipCopyAction.this.layerResolver != null) {
297+
for (String name : this.writtenLoaderEntries.getFiles()) {
298+
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name);
299+
this.layerIndex.add(layer, name);
300+
}
301+
}
305302
}
306303

307304
private boolean isInMetaInf(FileCopyDetails details) {
@@ -313,44 +310,75 @@ private boolean isInMetaInf(FileCopyDetails details) {
313310
}
314311

315312
private void writeJarToolsIfNecessary() throws IOException {
316-
if (BootZipCopyAction.this.layerResolver == null || !BootZipCopyAction.this.includeLayerTools) {
317-
return;
313+
if (BootZipCopyAction.this.layerToolsLocation != null) {
314+
writeJarModeLibrary(BootZipCopyAction.this.layerToolsLocation, JarModeLibrary.LAYER_TOOLS);
318315
}
319-
writeJarModeLibrary(JarModeLibrary.LAYER_TOOLS);
320316
}
321317

322-
private void writeJarModeLibrary(JarModeLibrary jarModeLibrary) throws IOException {
323-
String name = BootZipCopyAction.this.layerResolver.getPath(jarModeLibrary);
324-
writeFile(name, ZipEntryWriter.fromInputStream(jarModeLibrary.openStream()));
318+
private void writeJarModeLibrary(String location, JarModeLibrary library) throws IOException {
319+
String name = location + library.getName();
320+
writeEntry(name, ZipEntryWriter.fromInputStream(library.openStream()), false,
321+
(entry) -> prepareStoredEntry(library.openStream(), entry));
322+
if (BootZipCopyAction.this.layerResolver != null) {
323+
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(library);
324+
this.layerIndex.add(layer, name);
325+
}
325326
}
326327

327-
private void writeLayersIndexIfNecessary() throws IOException {
328-
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
329-
String layersIndex = (String) manifestAttributes.get("Spring-Boot-Layers-Index");
330-
if (layersIndex != null && BootZipCopyAction.this.layerResolver != null) {
331-
writeFile(layersIndex, ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding,
332-
BootZipCopyAction.this.layerResolver.getLayerNames()));
328+
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
329+
prepareStoredEntry(details.open(), archiveEntry);
330+
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
331+
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
333332
}
334333
}
335334

335+
private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException {
336+
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
337+
Crc32OutputStream crcStream = new Crc32OutputStream();
338+
int size = FileCopyUtils.copy(input, crcStream);
339+
archiveEntry.setSize(size);
340+
archiveEntry.setCompressedSize(size);
341+
archiveEntry.setCrc(crcStream.getCrc());
342+
}
343+
336344
private void writeClassPathIndexIfNecessary() throws IOException {
337345
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
338346
String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index");
339347
if (classPathIndex != null) {
340-
writeFile(classPathIndex,
341-
ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, this.writtenLibraries));
348+
writeEntry(classPathIndex,
349+
ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, this.writtenLibraries), true);
350+
}
351+
}
352+
353+
private void writeLayersIndexIfNecessary() throws IOException {
354+
if (BootZipCopyAction.this.layerResolver != null) {
355+
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
356+
String name = (String) manifestAttributes.get("Spring-Boot-Layers-Index");
357+
Assert.state(StringUtils.hasText(name), "Missing layer index manifest attribute");
358+
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name);
359+
this.layerIndex.add(layer, name);
360+
writeEntry(name, (entry, out) -> this.layerIndex.writeTo(out), false);
342361
}
343362
}
344363

345-
private void writeFile(String name, ZipEntryWriter entryWriter) throws IOException {
364+
private void writeEntry(String name, ZipEntryWriter entryWriter, boolean addToLayerIndex) throws IOException {
365+
writeEntry(name, entryWriter, addToLayerIndex, ZipEntryCustomizer.NONE);
366+
}
367+
368+
private void writeEntry(String name, ZipEntryWriter entryWriter, boolean addToLayerIndex,
369+
ZipEntryCustomizer entryCustomizer) throws IOException {
346370
writeParentDirectoriesIfNecessary(name, CONSTANT_TIME_FOR_ZIP_ENTRIES);
347371
ZipArchiveEntry entry = new ZipArchiveEntry(name);
348372
entry.setUnixMode(UnixStat.FILE_FLAG);
349373
entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES);
374+
entryCustomizer.customize(entry);
350375
this.out.putArchiveEntry(entry);
351376
entryWriter.writeTo(entry, this.out);
352377
this.out.closeArchiveEntry();
353-
378+
if (addToLayerIndex && BootZipCopyAction.this.layerResolver != null) {
379+
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name);
380+
this.layerIndex.add(layer, name);
381+
}
354382
}
355383

356384
private long getTime(FileCopyDetails details) {
@@ -360,6 +388,24 @@ private long getTime(FileCopyDetails details) {
360388

361389
}
362390

391+
/**
392+
* Callback interface used to customize a {@link ZipArchiveEntry}.
393+
*/
394+
@FunctionalInterface
395+
private interface ZipEntryCustomizer {
396+
397+
ZipEntryCustomizer NONE = (entry) -> {
398+
};
399+
400+
/**
401+
* Customize the entry.
402+
* @param entry the entry to customize
403+
* @throws IOException on IO error
404+
*/
405+
void customize(ZipArchiveEntry entry) throws IOException;
406+
407+
}
408+
363409
/**
364410
* Callback used to write a zip entry data.
365411
*/

0 commit comments

Comments
 (0)