Commit bad64004 authored by Andy Wilkinson's avatar Andy Wilkinson

Merge pull request #25571 from Jurrie

* gh-25571:
  Support Maven's reproducible build feature for war repackaging

Closes gh-25571
parents 9180220c 3c0e2365
...@@ -21,7 +21,6 @@ import java.io.IOException; ...@@ -21,7 +21,6 @@ import java.io.IOException;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.springframework.boot.loader.tools.Layouts.War;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
...@@ -102,9 +101,6 @@ public class Repackager extends Packager { ...@@ -102,9 +101,6 @@ public class Repackager extends Packager {
throws IOException { throws IOException {
Assert.isTrue(destination != null && !destination.isDirectory(), "Invalid destination"); Assert.isTrue(destination != null && !destination.isDirectory(), "Invalid destination");
Layout layout = getLayout(); // get layout early Layout layout = getLayout(); // get layout early
if (lastModifiedTime != null && layout instanceof War) {
throw new IllegalStateException("Reproducible repackaging is not supported with war packaging");
}
destination = destination.getAbsoluteFile(); destination = destination.getAbsoluteFile();
File source = getSource(); File source = getSource();
if (isAlreadyPackaged() && source.equals(destination)) { if (isAlreadyPackaged() && source.equals(destination)) {
......
...@@ -17,18 +17,20 @@ ...@@ -17,18 +17,20 @@
package org.springframework.boot.maven; package org.springframework.boot.maven;
import java.io.File; import java.io.File;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.stream.Collectors;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileSystemUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -78,17 +80,34 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests { ...@@ -78,17 +80,34 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests {
} }
@TestTemplate @TestTemplate
void whenWarIsRepackagedWithOutputTimestampTheBuildFailsAsItIsNotSupported(MavenBuild mavenBuild) void whenWarIsRepackagedWithOutputTimestampConfiguredThenWarIsReproducible(MavenBuild mavenBuild)
throws InterruptedException { throws InterruptedException {
mavenBuild.project("war-output-timestamp").executeAndFail((project) -> { String firstHash = buildWarWithOutputTimestamp(mavenBuild);
try { Thread.sleep(1500);
String log = FileCopyUtils.copyToString(new FileReader(new File(project, "target/build.log"))); String secondHash = buildWarWithOutputTimestamp(mavenBuild);
assertThat(log).contains("Reproducible repackaging is not supported with war packaging"); assertThat(firstHash).isEqualTo(secondHash);
}
private String buildWarWithOutputTimestamp(MavenBuild mavenBuild) {
AtomicReference<String> 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<String> 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);
} }
catch (Exception ex) { catch (IOException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
}); });
return warHash.get();
} }
@TestTemplate @TestTemplate
......
...@@ -148,7 +148,7 @@ public class RepackageMojo extends AbstractPackagerMojo { ...@@ -148,7 +148,7 @@ public class RepackageMojo extends AbstractPackagerMojo {
/** /**
* Timestamp for reproducible output archive entries, either formatted as ISO 8601 * Timestamp for reproducible output archive entries, either formatted as ISO 8601
* (<code>yyyy-MM-dd'T'HH:mm:ssXXX</code>) or an {@code int} representing seconds * (<code>yyyy-MM-dd'T'HH:mm:ssXXX</code>) or an {@code int} representing seconds
* since the epoch. Not supported with war packaging. * since the epoch.
* @since 2.3.0 * @since 2.3.0
*/ */
@Parameter(defaultValue = "${project.build.outputTimestamp}") @Parameter(defaultValue = "${project.build.outputTimestamp}")
......
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