Skip to content

Commit

Permalink
Improve zip file reproducibility
Browse files Browse the repository at this point in the history
We currently use the ZipArchiveEntry(File, String) constructor when
creating a zip file entry for Universal/packageBin. This constructor
reads mtime, atime, and ctime from the File and adds them to the 5455
extended header in the zip file. When we call setTime on the entry, it
only changes the mtime field--the atime and ctime are the same values
from the file and are likely to be different across builds and break
reproducibility.

To fix this, we use the ZipArchiveEntry(String) constructor which does
not read any file metadata, and only uses information we directly
provided to it. We now provie the source epoch via setLastModifiedTime
since that continues to use the 5455 extended header but only for mtime.
We also ensure directories have a trailing slash in the entry name,
since that was previously done by the other constructor.

With this change, when using SOURCE_DATE_EPOCH, zip files created with
Universal/packageBin are now byte-for-byte exactly the same.
  • Loading branch information
stevedlawrence committed Apr 15, 2024
1 parent 0f69b0c commit 25fe914
Showing 1 changed file with 8 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package universal

import java.net.URI
import java.nio.file.{FileSystem, FileSystems, Files, StandardCopyOption}
import java.nio.file.attribute.FileTime
import java.util.zip.Deflater

import org.apache.commons.compress.archivers.zip._
Expand Down Expand Up @@ -122,9 +123,14 @@ object ZipHelper {
IO createDirectory outputDir
withZipOutput(outputFile) { output =>
for (FileMapping(file, name, mode) <- sources) {
val entry = new ZipArchiveEntry(file, normalizePath(name))
val entryName = {
val n = normalizePath(name)
if (file.isDirectory && !n.endsWith("/")) n + "/" else n
}
val entry = new ZipArchiveEntry(entryName)
sys.env.get("SOURCE_DATE_EPOCH") foreach { epoch =>
entry.setTime(epoch.toLong * 1000)
val millis = epoch.toLong * 1000
entry.setLastModifiedTime(FileTime.fromMillis(millis))
}
// Now check to see if we have permissions for this sucker.
mode foreach (entry.setUnixMode)
Expand Down

0 comments on commit 25fe914

Please sign in to comment.