Summary
The spring-boot-maven-plugin repackage goal does not set a reproducible timestamp.
The provided timestamp is used but offset with the local timezone of the developer.
A project wants to be reproducible and sets the project.build.outputTimestamp to a desired value with a clear timezone in it.
Then the spring-boot-maven-plugin is used to repackage the jar/war file.
The outputTimestamp by default uses the correct timestamp from project.build.outputTimestamp.
The problem is that the timestamp in final jar/war file has this timestamp expressed in the timezone of the local build system. So someone in a different timezone can only reproduce the build if they are able to figure out the timezone of the original build system.
This makes the build needlessly hard to reproduce.
Reproduce
This mini project demonstrates this problem by building the same demo project twice in a docker image with a different timezone.
https://github.com/nielsbasjes/BugreportSpringPluginTimezone
Expected
I expect the output to be in the specified timezone in the outputTimestamp
This will make the end result always be the same regardless of the timezone of the build system.
Actual output:
For different timezones the output file is different
$ md5sum *jar
51101377d3c14d4def5607adc7694eef amsterdam.jar
bfc83311fec00946edde56b0313e642f eucla.jar
1208e63002398ff0606ab62a0b7f06a1 hawaii.jar
Snippets from diffoscope:
The content (all jar files and such) are identical
│┄ Archive contents identical but files differ, possibly due to different compression levels. Falling back to binary comparison.
In timezone Europe/Amsterdam
│ +drwxr-xr-x 2.0 unx 0 bX defN 11-Nov-11 23:22 META-INF/
│ +-rw-r--r-- 2.0 unx 379 bl defN 11-Nov-11 23:22 META-INF/MANIFEST.MF
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 23:22 org/
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 23:22 org/springframework/
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 23:22 org/springframework/boot/
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 23:22 org/springframework/boot/loader/
│ +-rw-r--r-- 2.0 unx 5667 bl defN 11-Nov-11 23:22 org/springframework/boot/loader/ClassPathIndexFile.class
│ +-rw-r--r-- 2.0 unx 7806 bl defN 11-Nov-11 23:22 org/springframework/boot/loader/ExecutableArchiveLauncher.class
│ +-rw-r--r-- 2.0 unx 2540 bl defN 11-Nov-11 23:22 org/springframework/boot/loader/JarLauncher.class
In timezone Australia/Eucla
│ -drwxr-xr-x 2.0 unx 0 bX defN 11-Nov-12 07:07 META-INF/
│ --rw-r--r-- 2.0 unx 379 bl defN 11-Nov-12 07:07 META-INF/MANIFEST.MF
│ -drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-12 07:07 org/
│ -drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-12 07:07 org/springframework/
│ -drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-12 07:07 org/springframework/boot/
│ -drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-12 07:07 org/springframework/boot/loader/
│ --rw-r--r-- 2.0 unx 5667 bl defN 11-Nov-12 07:07 org/springframework/boot/loader/ClassPathIndexFile.class
│ --rw-r--r-- 2.0 unx 7806 bl defN 11-Nov-12 07:07 org/springframework/boot/loader/ExecutableArchiveLauncher.class
│ --rw-r--r-- 2.0 unx 2540 bl defN 11-Nov-12 07:07 org/springframework/boot/loader/JarLauncher.class
In timezone AU/Hawaii
│ +drwxr-xr-x 2.0 unx 0 bX defN 11-Nov-11 12:22 META-INF/
│ +-rw-r--r-- 2.0 unx 379 bl defN 11-Nov-11 12:22 META-INF/MANIFEST.MF
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 12:22 org/
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 12:22 org/springframework/
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 12:22 org/springframework/boot/
│ +drwxr-xr-x 2.0 unx 0 bl defN 11-Nov-11 12:22 org/springframework/boot/loader/
│ +-rw-r--r-- 2.0 unx 5667 bl defN 11-Nov-11 12:22 org/springframework/boot/loader/ClassPathIndexFile.class
│ +-rw-r--r-- 2.0 unx 7806 bl defN 11-Nov-11 12:22 org/springframework/boot/loader/ExecutableArchiveLauncher.class
│ +-rw-r--r-- 2.0 unx 2540 bl defN 11-Nov-11 12:22 org/springframework/boot/loader/JarLauncher.class
Comment From: nielsbasjes
I did some digging and it seems the JarWriter gets to write a JarArchiveEntry which has the correct last modification timestamp (in epoch milliseconds).
Then during the actual writing in org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream a call is done to ZipUtil.toDosTime with a instance of Calendar which is initialized with Calendar.getInstance();.
This effectively means to fix this we have to change the default timezone of the application.
Based on that I put together the simplest fix I could think of in the mentioned merge request.