Skip to content

pin jar timestamps for reproducible builds - #198

Open
munzzyy wants to merge 1 commit into
guardianproject:masterfrom
munzzyy:reproducible-bundle-timestamps
Open

pin jar timestamps for reproducible builds#198
munzzyy wants to merge 1 commit into
guardianproject:masterfrom
munzzyy:reproducible-bundle-timestamps

Conversation

@munzzyy

@munzzyy munzzyy commented Aug 30, 2026

Copy link
Copy Markdown

Fixes #66

Two places were stamping build outputs with the current time instead of a fixed one:

tor-android-binary/build.gradle.kts

sourcesJar and javadocJar had no preserveFileTimestamps/reproducibleFileOrder settings, so building the same commit twice (or on two machines) produces jars that differ byte for byte, even though the content is identical. This exact settings block already existed once, in the old Groovy build.gradle (commit 7e1ff24, closing #45), but it got dropped when the project moved to Kotlin DSL. I restored it as a project-wide tasks.withType<AbstractArchiveTask>() block instead of duplicating it per task, so it also covers any other archive task added later.

I built both jars twice from a clean tree with --no-build-cache a few seconds apart and diffed the output:

./gradlew :tor-android-binary:clean
./gradlew --no-build-cache :tor-android-binary:sourcesJar :tor-android-binary:javadocJar
# copy jars aside, clean again, rebuild, then:
cmp sources-run1.jar sources-run2.jar   # identical
cmp javadoc-run1.jar javadoc-run2.jar   # identical
sha256sum matches on both

Dokka's javadoc output itself has no embedded generation timestamp (unlike the old raw javadoc tool the issue's diffoscope output was taken against), so pinning the jar tasks is enough to make both outputs reproducible.

tor-droid-make.sh

bundle() had a TODO for this. jar -cvf stamps every entry, including the auto-generated META-INF/MANIFEST.MF, with the current wall-clock time, so the bundle jar differs between runs even when the input files (aar, pom, sources jar, etc.) are unchanged. Recent JDKs (19+, this project already targets 24) support jar --create --date=<timestamp>, which pins the timestamp for every entry the tool writes, including the manifest. I used the same epoch already used for the native tor build in external/Makefile (SOURCE_DATE_EPOCH := 1234567890) so both halves of the release pin to the same instant.

I couldn't run the actual release() path locally since it needs the full NDK/tor native build plus a GPG key to sign with, so I verified the mechanism directly: built two jars from identical file content but different mtimes, once with the old jar -cvf (bytes differ) and once with jar --create --date=... (bytes identical), same as the failure mode in bundle().

Left out: the .aar itself isn't touched here, AGP handles its packaging separately and wasn't part of the TODO or the diffoscope output in the issue.

fixes guardianproject#66

sourcesJar and javadocJar in tor-android-binary/build.gradle.kts had
no preserveFileTimestamps/reproducibleFileOrder settings, so two
otherwise identical builds produce different jar bytes depending on
when the source checkout was made. This global tasks.withType block
existed in the old Groovy build.gradle (7e1ff24, for guardianproject#45) but
got dropped in the switch to Kotlin DSL.

The bundle() step in tor-droid-make.sh had the same problem: jar -cvf
stamps each entry, including the auto-generated META-INF/MANIFEST.MF,
with the current time. Switch to jar --create --date=<fixed> so the
staging bundle uploaded to Sonatype is also reproducible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

some timestamps are in javadoc and bundle.jar break reproducibility

1 participant