Skip to content

Commit 8f5ef95

Browse files
Use current timestamp for index files with Gradle
This commit removes changes the timestamp used when writing the classpath and layers index files in the Gradle plugin to be the current timestamp unless `preserveFileTimestamps=true`. It also polishes some duplication in the handling of entry attributes when creating the fat archive and adds a test to verify that the Gradle plugin uses the same fixed timestamp constant as Gradle uses internally. See gh-21005
1 parent b3ccefd commit 8f5ef95

File tree

4 files changed

+78
-68
lines changed

4 files changed

+78
-68
lines changed

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

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -183,15 +183,15 @@ private void closeQuietly(OutputStream outputStream) {
183183
*/
184184
private class Processor {
185185

186-
private ZipArchiveOutputStream out;
186+
private final ZipArchiveOutputStream out;
187187

188188
private final LayersIndex layerIndex;
189189

190190
private LoaderZipEntries.WrittenEntries writtenLoaderEntries;
191191

192-
private Set<String> writtenDirectories = new LinkedHashSet<>();
192+
private final Set<String> writtenDirectories = new LinkedHashSet<>();
193193

194-
private Set<String> writtenLibraries = new LinkedHashSet<>();
194+
private final Set<String> writtenLibraries = new LinkedHashSet<>();
195195

196196
Processor(ZipArchiveOutputStream out) {
197197
this.out = out;
@@ -224,23 +224,17 @@ private boolean skipProcessing(FileCopyDetails details) {
224224

225225
private void processDirectory(FileCopyDetails details) throws IOException {
226226
String name = details.getRelativePath().getPathString();
227-
long time = getTime(details);
228-
writeParentDirectoriesIfNecessary(name, time);
229227
ZipArchiveEntry entry = new ZipArchiveEntry(name + '/');
230-
entry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
231-
entry.setTime(time);
228+
prepareEntry(entry, name, getTime(details), UnixStat.FILE_FLAG | details.getMode());
232229
this.out.putArchiveEntry(entry);
233230
this.out.closeArchiveEntry();
234231
this.writtenDirectories.add(name);
235232
}
236233

237234
private void processFile(FileCopyDetails details) throws IOException {
238235
String name = details.getRelativePath().getPathString();
239-
long time = getTime(details);
240-
writeParentDirectoriesIfNecessary(name, time);
241236
ZipArchiveEntry entry = new ZipArchiveEntry(name);
242-
entry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
243-
entry.setTime(time);
237+
prepareEntry(entry, name, getTime(details), UnixStat.FILE_FLAG | details.getMode());
244238
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
245239
if (compression == ZipCompression.STORED) {
246240
prepareStoredEntry(details, entry);
@@ -257,13 +251,11 @@ private void processFile(FileCopyDetails details) throws IOException {
257251
}
258252
}
259253

260-
private void writeParentDirectoriesIfNecessary(String name, long time) throws IOException {
254+
private void writeParentDirectoriesIfNecessary(String name, Long time) throws IOException {
261255
String parentDirectory = getParentDirectory(name);
262256
if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) {
263-
writeParentDirectoriesIfNecessary(parentDirectory, time);
264257
ZipArchiveEntry entry = new ZipArchiveEntry(parentDirectory + '/');
265-
entry.setUnixMode(UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
266-
entry.setTime(time);
258+
prepareEntry(entry, parentDirectory, time, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
267259
this.out.putArchiveEntry(entry);
268260
this.out.closeArchiveEntry();
269261
}
@@ -293,8 +285,7 @@ private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOExc
293285
// Don't write loader entries until after META-INF folder (see gh-16698)
294286
return;
295287
}
296-
LoaderZipEntries loaderEntries = new LoaderZipEntries(
297-
BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES);
288+
LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime());
298289
this.writtenLoaderEntries = loaderEntries.writeTo(this.out);
299290
if (BootZipCopyAction.this.layerResolver != null) {
300291
for (String name : this.writtenLoaderEntries.getFiles()) {
@@ -320,37 +311,22 @@ private void writeJarToolsIfNecessary() throws IOException {
320311

321312
private void writeJarModeLibrary(String location, JarModeLibrary library) throws IOException {
322313
String name = location + library.getName();
323-
writeEntry(name, ZipEntryWriter.fromInputStream(library.openStream()), false,
314+
writeEntry(name, ZipEntryContentWriter.fromInputStream(library.openStream()), false,
324315
(entry) -> prepareStoredEntry(library.openStream(), entry));
325316
if (BootZipCopyAction.this.layerResolver != null) {
326317
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(library);
327318
this.layerIndex.add(layer, name);
328319
}
329320
}
330321

331-
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
332-
prepareStoredEntry(details.open(), archiveEntry);
333-
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
334-
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
335-
}
336-
}
337-
338-
private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException {
339-
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
340-
Crc32OutputStream crcStream = new Crc32OutputStream();
341-
int size = FileCopyUtils.copy(input, crcStream);
342-
archiveEntry.setSize(size);
343-
archiveEntry.setCompressedSize(size);
344-
archiveEntry.setCrc(crcStream.getCrc());
345-
}
346-
347322
private void writeClassPathIndexIfNecessary() throws IOException {
348323
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
349324
String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index");
350325
if (classPathIndex != null) {
351326
List<String> lines = this.writtenLibraries.stream().map((line) -> "- \"" + line + "\"")
352327
.collect(Collectors.toList());
353-
writeEntry(classPathIndex, ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, lines), true);
328+
writeEntry(classPathIndex, ZipEntryContentWriter.fromLines(BootZipCopyAction.this.encoding, lines),
329+
true);
354330
}
355331
}
356332

@@ -361,33 +337,65 @@ private void writeLayersIndexIfNecessary() throws IOException {
361337
Assert.state(StringUtils.hasText(name), "Missing layer index manifest attribute");
362338
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name);
363339
this.layerIndex.add(layer, name);
364-
writeEntry(name, (entry, out) -> this.layerIndex.writeTo(out), false);
340+
writeEntry(name, this.layerIndex::writeTo, false);
365341
}
366342
}
367343

368-
private void writeEntry(String name, ZipEntryWriter entryWriter, boolean addToLayerIndex) throws IOException {
344+
private void writeEntry(String name, ZipEntryContentWriter entryWriter, boolean addToLayerIndex)
345+
throws IOException {
369346
writeEntry(name, entryWriter, addToLayerIndex, ZipEntryCustomizer.NONE);
370347
}
371348

372-
private void writeEntry(String name, ZipEntryWriter entryWriter, boolean addToLayerIndex,
349+
private void writeEntry(String name, ZipEntryContentWriter entryWriter, boolean addToLayerIndex,
373350
ZipEntryCustomizer entryCustomizer) throws IOException {
374-
writeParentDirectoriesIfNecessary(name, CONSTANT_TIME_FOR_ZIP_ENTRIES);
375351
ZipArchiveEntry entry = new ZipArchiveEntry(name);
376-
entry.setUnixMode(UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
377-
entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES);
352+
prepareEntry(entry, name, getTime(), UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
378353
entryCustomizer.customize(entry);
379354
this.out.putArchiveEntry(entry);
380-
entryWriter.writeTo(entry, this.out);
355+
entryWriter.writeTo(this.out);
381356
this.out.closeArchiveEntry();
382357
if (addToLayerIndex && BootZipCopyAction.this.layerResolver != null) {
383358
Layer layer = BootZipCopyAction.this.layerResolver.getLayer(name);
384359
this.layerIndex.add(layer, name);
385360
}
386361
}
387362

388-
private long getTime(FileCopyDetails details) {
389-
return BootZipCopyAction.this.preserveFileTimestamps ? details.getLastModified()
390-
: CONSTANT_TIME_FOR_ZIP_ENTRIES;
363+
private void prepareEntry(ZipArchiveEntry entry, String name, Long time, int mode) throws IOException {
364+
writeParentDirectoriesIfNecessary(name, time);
365+
entry.setUnixMode(mode);
366+
if (time != null) {
367+
entry.setTime(time);
368+
}
369+
}
370+
371+
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
372+
prepareStoredEntry(details.open(), archiveEntry);
373+
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
374+
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
375+
}
376+
}
377+
378+
private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException {
379+
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
380+
Crc32OutputStream crcStream = new Crc32OutputStream();
381+
int size = FileCopyUtils.copy(input, crcStream);
382+
archiveEntry.setSize(size);
383+
archiveEntry.setCompressedSize(size);
384+
archiveEntry.setCrc(crcStream.getCrc());
385+
}
386+
387+
private Long getTime() {
388+
return getTime(null);
389+
}
390+
391+
private Long getTime(FileCopyDetails details) {
392+
if (!BootZipCopyAction.this.preserveFileTimestamps) {
393+
return CONSTANT_TIME_FOR_ZIP_ENTRIES;
394+
}
395+
if (details != null) {
396+
return details.getLastModified();
397+
}
398+
return null;
391399
}
392400

393401
}
@@ -414,41 +422,40 @@ private interface ZipEntryCustomizer {
414422
* Callback used to write a zip entry data.
415423
*/
416424
@FunctionalInterface
417-
private interface ZipEntryWriter {
425+
private interface ZipEntryContentWriter {
418426

419427
/**
420428
* Write the entry data.
421-
* @param entry the entry being written
422429
* @param out the output stream used to write the data
423430
* @throws IOException on IO error
424431
*/
425-
void writeTo(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException;
432+
void writeTo(ZipArchiveOutputStream out) throws IOException;
426433

427434
/**
428-
* Create a new {@link ZipEntryWriter} that will copy content from the given
429-
* {@link InputStream}.
435+
* Create a new {@link ZipEntryContentWriter} that will copy content from the
436+
* given {@link InputStream}.
430437
* @param in the source input stream
431-
* @return a new {@link ZipEntryWriter} instance
438+
* @return a new {@link ZipEntryContentWriter} instance
432439
*/
433-
static ZipEntryWriter fromInputStream(InputStream in) {
434-
return (entry, out) -> {
440+
static ZipEntryContentWriter fromInputStream(InputStream in) {
441+
return (out) -> {
435442
StreamUtils.copy(in, out);
436443
in.close();
437444
};
438445
}
439446

440447
/**
441-
* Create a new {@link ZipEntryWriter} that will copy content from the given
442-
* lines.
448+
* Create a new {@link ZipEntryContentWriter} that will copy content from the
449+
* given lines.
443450
* @param encoding the required character encoding
444451
* @param lines the lines to write
445-
* @return a new {@link ZipEntryWriter} instance
452+
* @return a new {@link ZipEntryContentWriter} instance
446453
*/
447-
static ZipEntryWriter fromLines(String encoding, Collection<String> lines) {
448-
return (entry, out) -> {
454+
static ZipEntryContentWriter fromLines(String encoding, Collection<String> lines) {
455+
return (out) -> {
449456
OutputStreamWriter writer = new OutputStreamWriter(out, encoding);
450457
for (String line : lines) {
451-
writer.append(line + "\n");
458+
writer.append(line).append("\n");
452459
}
453460
writer.flush();
454461
};

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@
2929
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
3030
import org.gradle.api.file.FileTreeElement;
3131

32+
import org.springframework.util.StreamUtils;
33+
3234
/**
3335
* Internal utility used to copy entries from the {@code spring-boot-loader.jar}.
3436
*
3537
* @author Andy Wilkinson
3638
* @author Phillip Webb
39+
* @author Scott Frederick
3740
*/
3841
class LoaderZipEntries {
3942

@@ -84,11 +87,7 @@ private void prepareEntry(ZipArchiveEntry entry, int unixMode) {
8487
}
8588

8689
private void copy(InputStream in, OutputStream out) throws IOException {
87-
byte[] buffer = new byte[4096];
88-
int bytesRead = -1;
89-
while ((bytesRead = in.read(buffer)) != -1) {
90-
out.write(buffer, 0, bytesRead);
91-
}
90+
StreamUtils.copy(in, out);
9291
}
9392

9493
/**

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
4141
import org.apache.commons.compress.archivers.zip.ZipFile;
4242
import org.gradle.api.Project;
43+
import org.gradle.api.internal.file.archive.ZipCopyAction;
4344
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
4445
import org.gradle.api.tasks.bundling.Jar;
4546
import org.gradle.testfixtures.ProjectBuilder;
@@ -56,6 +57,7 @@
5657
*
5758
* @param <T> the type of the concrete BootArchive implementation
5859
* @author Andy Wilkinson
60+
* @author Scott Frederick
5961
*/
6062
abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
6163

@@ -330,6 +332,12 @@ void fileTimestampPreservationCanBeDisabled() throws IOException {
330332
}
331333
}
332334

335+
@Test
336+
void constantTimestampMatchesGradleInternalTimestamp() {
337+
assertThat(BootZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES)
338+
.isEqualTo(ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES);
339+
}
340+
333341
@Test
334342
void reproducibleOrderingCanBeEnabled() throws IOException {
335343
this.task.setMainClassName("com.example.Main");

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,10 @@ void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException {
101101
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
102102
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/classes/com/example/Application.class",
103103
"BOOT-INF/classes/application.properties", "BOOT-INF/classes/static/test.css");
104-
ZipEntry layersIndexEntry = jarFile.getEntry("BOOT-INF/layers.idx");
105-
assertThat(layersIndexEntry.getTime()).isEqualTo(BootZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES);
106104
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
107105
assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader",
108106
"snapshot-dependencies", "application");
109107
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
110-
ZipEntry layerToolsEntry = jarFile.getEntry(layerToolsJar);
111-
assertThat(layerToolsEntry.getTime()).isEqualTo(BootZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES);
112108
List<String> expected = new ArrayList<>();
113109
expected.add("- \"dependencies\":");
114110
expected.add(" - \"BOOT-INF/lib/first-library.jar\"");

0 commit comments

Comments
 (0)