Automate package release process via GitHub Actions - #505
Draft
psobot wants to merge 6 commits into
Draft
Conversation
Cutting a release previously meant editing the version in three places
(pedalboard/version.py, pyproject.toml, uv.lock) and then manually
creating a release through the GitHub UI. Now it's one line.
Single source of truth
----------------------
pyproject.toml switches to `dynamic = ["version"]`, reading the version
out of pedalboard/version.py via scikit-build-core's regex metadata
provider. setup.py already read that same file, so version.py is now the
only place a version string appears.
uv.lock no longer pins a version either: uv deliberately omits the
version field for dynamic source trees ("Omit versions for dynamic
source trees", uv-resolver/src/lock/mod.rs), so future bumps won't make
the lockfile stale and `uv sync --locked` keeps working untouched.
Automatic releases
------------------
A new `detect-release` job notices when a push to master changes
__version__. When it does, that same workflow run builds all wheels,
runs the full test and ASan suites, uploads to PyPI, and finally tags
the commit and creates the GitHub release with generated notes.
Everything deliberately stays inside one workflow run: releases created
with the default GITHUB_TOKEN do not trigger new workflow runs, so a
separate "create the release" workflow could never have kicked off the
wheel builds. Keeping it in-run also means no PAT is needed.
The existing manual path (publish a release in the UI) still works, and
nothing is published unless __version__ actually changes. Re-releasing an
existing tag is skipped with a warning rather than failing.
`cancel-in-progress` is now limited to pull requests. A push to master
can publish to PyPI, and cancelling that partway through would leave a
half-finished release.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ND3oQGgms3aXVsoBikUr93
Two follow-ups to the release automation. Logic out of YAML ----------------- The detect-release job was a 40-line inline Bash block, which was both hard to read and impossible to test. It's now scripts/detect_release.py, with the decision logic in pure functions and 29 tests in tests/test_detect_release.py covering version parsing, validation, and every branch of the release decision. The tests load the script by path rather than importing it as a package, so they also pass when tests/ is run from a temporary directory during post-wheel-build testing. Guarding against accidental releases ------------------------------------ Merging a version bump publishes to PyPI irreversibly, and it would be easy to do that without realising. Two defences: Any pull request that changes __version__ now says so. The job emits a ::warning:: against version.py and writes a note to the run summary spelling out that merging will publish, and that PyPI releases can be yanked but never replaced. The upload-pypi job now runs in the "PyPI Deployment" environment, which already existed in this repo but was unused and had no protection rules. Adding a required reviewer to it makes publishing pause for approval; until then behaviour is unchanged. Moving PYPI_DEPLOY_TOKEN from a repo-level secret into that environment would further ensure no other job -- or future workflow change -- can publish at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND3oQGgms3aXVsoBikUr93
The "PyPI Deployment" environment has no protection rules, and nobody on the project currently has the admin access needed to add a required reviewer or move PYPI_DEPLOY_TOKEN into it. An environment reference with no protection rules fails open silently: it reads like an approval gate to anyone scanning the workflow, while gating nothing at all. That's worse than not having one, so it's removed rather than left as decoration, with a comment recording why. The safeguard against an accidental release is now solely the warning that detect-release puts on any pull request changing __version__. That's advisory rather than enforcing, and the docs say so plainly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND3oQGgms3aXVsoBikUr93
The docs were rebuilt and deployed on every push to master, so the published documentation described whatever was on master rather than what people can actually install from PyPI. Filter the push trigger to pedalboard/version.py. That file holds nothing but the version, so touching it is as good a signal as parsing it, and a version bump always changes it -- no release can slip through without its docs. The existing workflow_dispatch trigger still covers docs-only fixes that shouldn't wait for a release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND3oQGgms3aXVsoBikUr93
The previous commit filtered the docs deploy on changes to pedalboard/version.py, on the assumption that the file holds nothing but the version. It doesn't: it also carries a copyright header whose year range gets bumped annually, usually by a sweep across the whole repo. That would have deployed the docs with no version bump, publishing documentation for an unreleased master -- exactly what gating the docs was meant to prevent. detect_release.py now also reports version-changed, distinct from should-release: the version can change without the run being allowed to publish (a pull request, or a tag that already exists), and the docs should follow the version rather than the release permission. deploy-docs.yml keeps the path filter as a cheap pre-filter -- a version bump always touches that file -- and adds a check-version-bump job that confirms __version__ actually changed. Manual runs still deploy unconditionally, so docs-only fixes don't need a release. Tested both ways: editing only the copyright year yields version-changed=false, and a real bump yields true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND3oQGgms3aXVsoBikUr93
Gating the docs workflow on its own version check was the wrong shape. It answered "did the version change?" when the question is "did we publish?", and those differ: a push whose tag already exists changes the version but publishes nothing, and the separate workflow ran in parallel with the upload anyway, so the docs could go live for a version that then failed to reach PyPI. deploy-docs.yml becomes a reusable workflow (workflow_call), and all.yml calls it from a job that needs upload-pypi. That dependency is the entire gate: if upload-pypi is skipped because this isn't a release, or fails because PyPI rejected the wheels, the docs are never published. The docs now describe exactly what's installable, which is the point. This also deletes the duplicated gate: no second detect job, and detect_release.py no longer emits version-changed, since nothing consumes it. version_changed stays as an internal field because the "merging this publishes" warning on pull requests still needs it. deploy-docs.yml keeps workflow_dispatch, so docs-only fixes can still be published without cutting a release. Its push and tag triggers are gone: the push trigger is superseded, and the tag trigger never fired anyway, since tags created with GITHUB_TOKEN don't trigger workflows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND3oQGgms3aXVsoBikUr93
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.