From c4a55a5fb421dadf074fb39eddd542cf29a43deb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 16 Mar 2020 20:29:37 +0000 Subject: [PATCH] Fail fast when attempting to repackage a reproducible war Maven's war plugin does not support reproducible builds, resulting in the entries in the war file not being written in a consistent order from build to build. Closes gh-20176 --- .../boot/loader/tools/Repackager.java | 4 ++ .../boot/maven/WarIntegrationTests.java | 38 ++++--------------- .../boot/maven/RepackageMojo.java | 2 +- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index 274783704c..2493473703 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.nio.file.attribute.FileTime; import java.util.jar.JarFile; +import org.springframework.boot.loader.tools.Layouts.War; import org.springframework.util.Assert; /** @@ -100,6 +101,9 @@ public class Repackager extends Packager { public void repackage(File destination, Libraries libraries, LaunchScript launchScript, FileTime lastModifiedTime) throws IOException { Assert.isTrue(destination != null && !destination.isDirectory(), "Invalid destination"); + if (lastModifiedTime != null && getLayout() instanceof War) { + throw new IllegalStateException("Reproducible repackaging is not supported with war packaging"); + } destination = destination.getAbsoluteFile(); File source = getSource(); if (isAlreadyPackaged() && source.equals(destination)) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java index 38c9990a89..c14af4d532 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java @@ -17,17 +17,12 @@ package org.springframework.boot.maven; import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.jar.JarFile; -import java.util.stream.Collectors; +import java.io.FileReader; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.loader.tools.FileUtils; -import org.springframework.util.FileSystemUtils; +import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -72,34 +67,17 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests { } @TestTemplate - void whenWarIsRepackagedWithOutputTimestampConfiguredThenWarIsReproducible(MavenBuild mavenBuild) + void whenWarIsRepackagedWithOutputTimestampTheBuildFailsAsItIsNotSupported(MavenBuild mavenBuild) throws InterruptedException { - String firstHash = buildWarWithOutputTimestamp(mavenBuild); - Thread.sleep(1500); - String secondHash = buildWarWithOutputTimestamp(mavenBuild); - assertThat(firstHash).isEqualTo(secondHash); - } - - private String buildWarWithOutputTimestamp(MavenBuild mavenBuild) { - AtomicReference warHash = new AtomicReference<>(); - mavenBuild.project("war-output-timestamp").execute((project) -> { - File repackaged = new File(project, "target/war-output-timestamp-0.0.1.BUILD-SNAPSHOT.war"); - assertThat(repackaged).isFile(); - assertThat(repackaged.lastModified()).isEqualTo(1584352800000L); - try (JarFile jar = new JarFile(repackaged)) { - List unreproducibleEntries = jar.stream() - .filter((entry) -> entry.getLastModifiedTime().toMillis() != 1584352800000L) - .map((entry) -> entry.getName() + ": " + entry.getLastModifiedTime()) - .collect(Collectors.toList()); - assertThat(unreproducibleEntries).isEmpty(); - warHash.set(FileUtils.sha1Hash(repackaged)); - FileSystemUtils.deleteRecursively(project); + mavenBuild.project("war-output-timestamp").executeAndFail((project) -> { + try { + String log = FileCopyUtils.copyToString(new FileReader(new File(project, "target/build.log"))); + assertThat(log).contains("Reproducible repackaging is not supported with war packaging"); } - catch (IOException ex) { + catch (Exception ex) { throw new RuntimeException(ex); } }); - return warHash.get(); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index bd61c994bb..622ebc51c6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -146,7 +146,7 @@ public class RepackageMojo extends AbstractPackagerMojo { /** * Timestamp for reproducible output archive entries, either formatted as ISO 8601 * (yyyy-MM-dd'T'HH:mm:ssXXX) or an {@code int} representing seconds - * since the epoch. + * since the epoch. Not supported with war packaging. * @since 2.3.0 */ @Parameter(defaultValue = "${project.build.outputTimestamp}")