-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Make JAR builds reproducible #2217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make JAR builds reproducible #2217
Conversation
sormuras
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm -- @marcphilipp should take a look, too.
|
Tentatively slated for 5.7 M1 for team discussion |
Since all other jars use |
|
As far as I'm aware, I imagine something that loops over the contents of the archive and sets last modified to the value of |
Codecov Report
@@ Coverage Diff @@
## master #2217 +/- ##
=========================================
Coverage 91.21% 91.21%
Complexity 4466 4466
=========================================
Files 383 383
Lines 10724 10724
Branches 869 869
=========================================
Hits 9782 9782
Misses 728 728
Partials 214 214 Continue to review full report at Codecov.
|
|
@marcphilipp - I believe that the most recent commit solves the issue for the two special-case sub-projects. |
marcphilipp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
|
Those commits have been re-written, and the test script only returns a diff for The jar-rewriting has been moved to its own file under |
|
This looks like it's almost ready to go - is there a sensible place to record this feature in the User Guide / Release Notes? (Last remaining unchecked box) |
|
I'd say as a new section in the appendix. WDYT? |
|
That sounds good, will add something there. I've also added another GitHub action which will perform multiple EDIT: This is now done and working - ready for re-review @marcphilipp |
This change uses Gradle's reproducible archives feature (https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives) to consistently build the output JARs. Additionally, the build now supports SOURCE_DATE_EPOCH to allow overriding of `buildTimeAndDate` as this introduces non-determinism into the build.
This uses the same fixed-time constant as is being used when `isPreserveFileTimestamps` is set to false.
marcphilipp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, only found two minor things!
|
Thanks for the feedback, made changes as suggested. 👍 |
marcphilipp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
Thanks a lot, @mrwilson! 🎉 |
|
Glad to see this merged in! Great work @mrwilson. 🎉 |
|
Frankly speaking, I see no reason to enforce timestamps in jar files. It is required to capture the build environment: timestamp, and Java version/vendor. Even though jar most jar files have Build-Date and Build-Time headers, it is not clear how to reproduce JUnit build. |
| val jarEntry = JarEntry(entry.name) | ||
|
|
||
| // Use the same constant as the fixed timestamps in normal copy actions | ||
| jarEntry.time = ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrwilson , this seems to specify the same constant value for all the jars. Is isPreserveFileTimestamps = false enough then? What is the purpose of rewriting the jar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just for two specific cases where the actual jar command is being used to manipulate the artifact after it's been created by Gradle, which alters the timestamps and makes the build non-reproducible - I tried to replicate what the jar tool was doing in pure Gradle, with no success.
As an immediate solution, this resets all the timestamps of the two artifacts in question - the choice of timestamp was informed by what the isPreserveFileTimestamps = false would do if this step wasn't necessary.
I hope that helps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that clarifies things. It would probably be great if something like that was in the comment above.
|
FYI, I just added Gradle builds support to reproducible-central |
|
The results linked via 🔍 show differences like:
Seems like not all "external" properties are stable - or not all expected differences are excluded from the comparison. What is the latest result for JUnit 5.10.1? |
with my limited knowledge of Gradle, I can't make the build work: I need help Do you know how I can fix this? |
|
The command we use in checkBuildReproducibility.sh reads:
Does this help? |
|
I tried to run this command: gives build success, but I don't know what this success means Then I tried to modify source code and rebuild: same success, even if the expected result for modified code is failure because I obvioulsy should not get the same binaries are the 5.10.1 release then I don't know what this command does, it it does not do what I expect = check local build output against content downloaded from Maven Central I'll try to have a look at |
|
@hboutemy Our release builds currently require signing but you can use any PGP key you want. The key id in your local GPG can be configured by passing |
|
thanks @marcphilipp for helping: ok, I need to drop the another question: is there a way to configure the |
|
for now, I checked manually (please continue helping me on the for the |
Set the |
|
@hboutemy Could you please raise a new issue where we can discuss dropping the |
Yes, you can call |
I've just added an option to disable signing that will help with future releases: 6238d55 |
Perhaps we can store the value used for |
In practice, different JVM can produce different jars, so having a detailed JVM version does help. |
|
@visi #3559 created, as asked by @marcphilipp and thanks @marcphilipp for your hints, I'll be able to improve my Gradle rebuilding script in Reproducible Central to check more projects in the future |
Overview
This PR makes the java bytecode generated by this project reproducible. This means that under identical builds condition (for example Java version), repeated builds should provide the same output byte-for-byte.
It achieves this by:
SOURCE_DATE_EPOCHas a way of fixing build-time-specific metadata added to JARsjarto rewrite their contents after build.Confirming the change
The diff should be empty, that is to say, both independent runs of the build should have generated byte-for-byte identical outputs.
Omitting Javadocs
The javadocs outputs remain non-deterministic - despite the inclusion of
noTimestamp(true)in the project's javadoc configuration, the comment header is still being generated. It looks like the javadoc mechanism in Gradle isn't honouring this flag.I wasn't able to solve this problem, but would be necessary to achieve full reproducibility.
I hereby agree to the terms of the JUnit Contributor License Agreement. ✅
Definition of Done
@APIannotations