The release process¶
This page describes how a release of wasi-python is cut: the version bump, the release workflow, how the tests are split, how the assets are signed, and how they are published. Only the project's maintainer cuts a release.
Bump the version¶
The version lives in one place: __version__ in src/wasi_python/__init__.py. pyproject.toml reads it from there. The script release/version.py changes it:
uv run python release/version.py bump # the next version, and a CHANGELOG.md section
uv run python release/version.py release --push # date the section, tag v<version>, push: the release runs
A release is the annotated tag v<version> on a pushed commit of main. Pushing the tag starts the release workflow, .github/workflows/release.yml.
What the release workflow does¶
The workflow runs these stages, in order:
- Build. It builds the artifact set from the pins on arm64 macOS. It checks that every source tree is its pinned tarball plus exactly the recorded patches.
- Rebuild. Two more jobs rebuild the set from the pins, one on an arm64 Linux runner and one on an x86_64 Linux runner. Each must produce the same
SHA256SUMSand the same wheel, byte for byte. Nothing is signed or published until they match. - Test in shards. The tests run in parallel shards (slices of the test suite, each a job of its own) on macOS, Linux and Windows at once. The packages' own test suites run in the guest on Linux. One group of numpy's and pandas' tests, and CPython's file-system group, also run on macOS and Windows.
tests/release_shards.tomllists the shards, anduv run pytest --shard=<name>runs one. - Gather. A job named
gatherreads every shard's record. Nothing is signed or published unless every shard ran exactly its share on the release's bytes and nothing failed. - Attest. An
attestjob signs build provenance for every asset and verifies it strictly. - Publish. The workflow publishes a GitHub release with the wheel, the artifact set as
.tar.xz, and theirSHA256SUMS. It uploads the wheel to PyPI aswasi-pythonthrough PyPI's trusted publishing. A version with ana,borrcsuffix is published as a pre-release.
Two tiers of tests¶
The tests have two tiers:
- The normal tier runs on every check of a change.
- The release tier is the slow tests: numpy's and pandas' slow cases, contourpy's slow cases, CPython's tests that need the
cpuresource, and the whole suite of every pure-Python package the product ships. It runs only before a publication.
Together the tiers hold every test of every package built. The project's rules check that each test lands in exactly one tier, so nothing is left out of both.
Every job uses the bytes the build made¶
Every job that checks, signs or publishes uses exactly the bytes the build job made. The build job hands on its upload's artifact id and the SHA-256 of its SHA256SUMS, and prints both in its log. Each later job downloads by that id and checks:
SHA256SUMSagainst the hash;- every file against
SHA256SUMS; - that nothing else is there.
Only then does the job act. Each job holds only the permissions it uses, and every action is pinned by a full commit.
Signing¶
Build provenance is signed by the release workflow at the release's tag, and recorded in Sigstore's public log. If the attest job fails, nothing is published. Re-run the failed jobs of the same workflow run; its artifacts are kept for 90 days. Never push the tag again. If a re-run stops at a guard ("handed on no artifact id"), re-run all jobs: the build makes the same bytes again.
Users check a signed asset as Verify a download describes.
Dry runs¶
A dry run is a manual run of the release workflow. It builds, rebuilds and tests exactly as a release does, but it never signs and never publishes. A signature is permanent in Sigstore's public log, so only a real release signs. A release tag goes only on a commit whose dry run passed on all three systems.
After publishing¶
Whoever cut the release compares the published assets with the SHA256SUMS in the build job's log: the GitHub release's assets (gh release download, then shasum -a 256 -c) and PyPI's wheel digest. The result goes in the release's notes.