Skip to content

The build is reproducible, and CI checks that it is - #183

Merged
rohanrkamath merged 3 commits into
mainfrom
ci/reproducible-build
Sep 13, 2026
Merged

rohanrkamath merged 3 commits into
mainfrom
ci/reproducible-build

Conversation

@arpanghoshal

Copy link
Copy Markdown
Member

What this changes

  • Every python -m build in ci.yml, publish.yml and release.yml runs with SOURCE_DATE_EPOCH set to the commit's own timestamp, so a wheel built from the same commit with the backend the lock names is the same bytes wherever it is built.
  • scripts/normalize_sdist.py (new) rewrites the sdist deterministically: members sorted, mtimes set to the epoch, owner 0:0, modes normalised, gzip header without name or timestamp. Every member and every byte of content is kept; twine check --strict still passes and the package job's "sdist can run its own tests" step extracts it as before.
  • CI's package job builds a second time and diffs the sha256sum of both directories. Reproducible is a claim until something checks it.
  • CONTRIBUTING.md, Releases: how anyone rebuilds a release and compares it with PyPI.

Measured on this machine before the change: two builds of the same commit gave an identical wheel and two different sdists (tar mtimes and the gzip timestamp). After normalisation the two sdists are one hash. The property holds for every release cut after this merges; earlier releases were built without SOURCE_DATE_EPOCH.

This is the build_repeatable (silver) and build_reproducible (gold) pair of the OpenSSF Best Practices criteria.

Checklist

  • Specification first. No kernel behaviour; release process only.
  • Tests first. test_every_build_in_a_workflow_is_reproducible reads every workflow step that builds and requires the export before and the normaliser after; two tests drive normalize_sdist.py on synthetic tarballs (two differing inputs become one byte string with content preserved; refuses a wheel and refuses to run without an epoch).
  • Mutation table. Below.
  • CLAIMS.md. No README sentence changes.
  • Docs audit green. Nothing under docs/ changes.
  • scripts/check.sh green for what this touches: ruff, mypy --strict on the script, the repository suite.
  • Independent review not needed: no authorization, identity, gateway, adapter or store code.
  • Signed off.

Mutation table

Guard Test Result
export SOURCE_DATE_EPOCH line removed from release.yml test_every_build_in_a_workflow_is_reproducible red, names the step; restored, green
normalize_sdist.py call removed from publish.yml same red; restored, green
member.mtime = epoch removed from the normaliser test_normalize_sdist_makes_two_builds_of_the_same_tree_identical red on the mtime set; restored, green

🤖 Generated with Claude Code

Every python -m build in a workflow now runs with SOURCE_DATE_EPOCH set to the
commit's own timestamp, so the wheel is the same bytes wherever it is built;
scripts/normalize_sdist.py does for the sdist what setuptools does not (copy
times, uid, filesystem order, the gzip header), keeping every member and every
byte of content. CI's package job builds a second time and diffs the hashes,
so the property is checked on every pull request rather than claimed after a
tag. CONTRIBUTING.md's Releases section says how to rebuild a release and
compare it with PyPI.

Measured before this change: two local builds of the same commit gave the
same wheel and different sdists; after normalisation, the same sdist.

Signed-off-by: arpan <contact@arpanghoshal.com>
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 30 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4ff0e6b8-35fa-4f60-9d83-8126f948f649

📥 Commits

Reviewing files that changed from the base of the PR and between be45b8f and b79eb94.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • .github/workflows/publish.yml
  • .github/workflows/release.yml
  • CONTRIBUTING.md
  • scripts/normalize_sdist.py
  • tests/test_repository_signals.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rohanrkamath
rohanrkamath merged commit 5dbcfe2 into main Sep 13, 2026
13 of 14 checks passed
@rohanrkamath
rohanrkamath deleted the ci/reproducible-build branch September 13, 2026 21:24
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.

2 participants