Reproducible Wheel Builds
The wheel published to pypi.org is
built via hatch build, using hatchling and hatch-requirements-txt as
the PEP 518 build backend ([build-system] in pyproject.toml). This
page covers how that build is made hash-verified and provably
bit-identical across independent builds of the same commit.
Hash-pinning the build dependencies
packaging/requirements-build.txt pins every build-time dependency
(hatchling, hatch-requirements-txt, and their transitive deps —
packaging, pathspec, pluggy, tomlkit, trove-classifiers,
editables) to an exact version and a sha256 hash. Passing it as a
--build-constraint
makes pip verify every installed build-tool file against that hash before
using it, rather than trusting whatever the index currently serves:
pip wheel --build-constraint requirements-build.txt --no-deps .
[build-system].requires in pyproject.toml deliberately stays
unpinned (["hatchling", "hatch-requirements-txt"]) — the constraint
file is the single source of truth for exact versions, so there’s nothing
to keep in sync by hand.
All of hatchling’s and hatch-requirements-txt’s build-time
dependencies are pure-Python packages with a single universal
(py3-none-any) wheel, so pip-compile --generate-hashes already
resolves exactly one hash per package on its own — no wheelhouse detour
needed (unlike packages with several platform-specific wheels, e.g. C
extensions, where an unrestricted --generate-hashes would pin every
platform variant at once).
Regenerate the file after changing [build-system].requires or to pick
up newer build-tool releases:
packaging/update-requirements.sh # keep existing pins stable
packaging/update-requirements.sh --upgrade # move pins forward
Proving bit-identical builds
Hash-pinning proves every installed file is the one you expect — it doesn’t by itself prove the build process is deterministic. Run:
packaging/verify-reproducible-build.sh
This builds the wheel twice, independently, and compares
sha256sum. It should print:
OK: bit-identical wheel across two independent builds (<hash>)
Not part of the regular lint/test loop — building the wheel twice is too slow for the everyday dev loop. Run it as a pre-release check instead.
Known limits / not covered here
The AppImage build (
sshmitm-x86_64.AppImage) goes through the separateappimagepackage, which handles its own reproducibility concerns independently of this page.Snap builds are not covered — snapcraft’s container-based build has no direct equivalent to
--build-constraint/SOURCE_DATE_EPOCH, and achieving bit-identical snaps would be a separate effort.The runtime dependency set (
requirements.txt) is version-pinned but not hash-pinned — that affects the wheel’sRequires-Distmetadata, not the wheel bytes themselves, and is out of scope here.CI does not yet run
verify-reproducible-build.shas part of the release workflow (python-publish.yml). Wiring it in is planned alongside a move to Trusted Publishing, which replaces the long-livedTWINE_PASSWORDtoken currently used there.