Skip to content

Latest commit

 

History

History
116 lines (94 loc) · 6.14 KB

File metadata and controls

116 lines (94 loc) · 6.14 KB

Releasing processkit

The release is a manual, multi-platform publish to PyPI, driven by .github/workflows/release.yml (workflow_dispatch-only — it never runs on its own). It builds the wheel matrix + sdist, publishes to PyPI, pushes the version commit + tag to main, and creates a GitHub Release. Publishing uses PyPI Trusted Publishing (OIDC) — no long-lived secret — and attaches PEP 740 provenance attestations.

One-time setup

  1. Configure a PyPI Trusted Publisher (the recommended, secret-free path). On https://pypi.org/manage/account/publishing/ add a GitHub publisher. For the first release the project does not exist yet, so use the pending publisher form there:

    • PyPI Project Name: processkit-py (the bare processkit is taken on PyPI; the import name is still processkit).
    • Owner / Repository: this repo.
    • Workflow name: release.yml (the filename, exactly).
    • Environment name: leave blank. The publish job sets no GitHub environment, so filling this in makes the OIDC subject mismatch and PyPI rejects the upload.

    See https://docs.pypi.org/trusted-publishers/. Nothing else is needed — release.yml already grants id-token: write and mints the credential per run.

    Fallback (no trusted publisher): set a PYPI_API_TOKEN repository secret to a PyPI API token; the publish action uses it instead. (Trusted publishing is preferred — no rotation, no secret to leak.)

  2. (Optional) TestPyPI dry-run publisher. To use test-release.yml, add the same kind of publisher on https://test.pypi.org/manage/account/publishing/ with workflow name test-release.yml (Environment blank), or set a TESTPYPI_API_TOKEN secret.

  3. GitHub App for the push to protected mainrequired (main has branch protection). The release pushes the version commit + v<version> tag to main as the ZelAnton-release-bot App, which sits in the ruleset's bypass list; the default github-actions[bot] cannot be granted a ruleset bypass (system actor, not an App). The repo variable RELEASE_APP_ID (3951739, the shared App) is already set — add the secret RELEASE_APP_PRIVATE_KEY = the App's .pem private key (the same App/key as the sibling repos). See release-token-bypass.md. Until the secret is set the App step is skipped and the push falls back to GITHUB_TOKEN, which the protection rejects — so set it before the first release.

Release toolchain snapshot

scripts/release/toolchain.env is the single source of truth for the exact cibuildwheel, maturin, twine, and Rust versions used to build and check release artifacts. Both release workflows load it, and the reusable matrix passes the same Rust version to host builds and Linux containers. The wheel build installs the snapshot's maturin into each fresh build environment before running PEP 517 without a second dependency resolution. rust-toolchain.toml mirrors the Rust pin for local development but does not independently select the release toolchain.

Update the snapshot deliberately: verify the selected releases in PyPI and the official Rust stable channel, edit scripts/release/toolchain.env, mirror its Rust version in rust-toolchain.toml, and run:

uv run python scripts/release/toolchain.py verify
uv run pytest tests/test_release_scripts.py

The verifier fails on floating ranges, stable, missing consumers, or drift between the snapshot and rust-toolchain.toml. Finish by dispatching the TestPyPI dry-run; only its full matrix proves the new snapshot on every release target.

Cutting a release

  1. Dry-run first (recommended): Actions → Test release (TestPyPI)Run workflow. This builds the full wheel matrix (manylinux + musllinux + macOS + Windows) and uploads to TestPyPI — the only thing that exercises the real cibuildwheel build + OIDC upload path. Fix any failure before the real run.
  2. Release: Actions → ReleaseRun workflow (from main) → pick the bump (patch / minor / major; ignored on the first release, which seeds the version from pyproject.toml). The version is never typed by hand — the latest v* tag drives the next number.

The pipeline then: computes the version + release notes → builds wheels + sdist → strict twine checkpublishes to PyPI (the single irreversible pivot) → atomically pushes the version commit + v<version> tag to main → creates the GitHub Release (wheels + sdist + SHA256SUMS).

  1. After the first release: uncomment the PyPI badge in README.md, and refresh the README prose that still says the release is pending (the build-from-source intro and the "first release to PyPI is pending" note). Done for v1.0.0 — not needed again for subsequent releases.

Docs site

The guides in docs/ render as an mdBook site at https://zelanton.github.io/processkit-py/. On a push to main, docs.yml builds the book, validates its rendered local links and anchors, and publishes it to GitHub Pages. Pull requests run the same build and checks but never deploy.

Preview the docs locally with mdbook serve --open, or run just docs for the same build-and-link-check sequence CI uses.

If a release fails

The ordering is built so failures are safe to recover:

  • Before or at the PyPI publish — nothing was pushed to the remote (and no tag exists yet, so a re-run recomputes the same version). Just re-run the workflow; skip-existing makes any file a partial upload already landed a no-op.
  • After the tag is pushed to main — the package is on PyPI and the tag is on main. Do not re-run the whole workflow (a re-run computes the next version and orphans this release). Finish by hand: the failing step prints the exact gh release command to run.
  • The atomic tag push can lose a race with an ordinary push to main that lands mid-run (it leaves package-on-PyPI / tag-not-pushed). Cut releases when main is quiet; if it happens, push the tag + version commit by hand, then create the Release as above.