Automate release pipeline with towncrier-fragments version scheme#682
Open
RonnyPfannschmidt wants to merge 8 commits into
Open
Automate release pipeline with towncrier-fragments version scheme#682RonnyPfannschmidt wants to merge 8 commits into
RonnyPfannschmidt wants to merge 8 commits into
Conversation
Replace the manual release process with an automated CI pipeline: - Configure setuptools_scm with the `towncrier-fragments` version scheme (from vcs-versioning, bundled with setuptools-scm>=10.0) to derive semver bumps from changelog fragment types automatically. - Add `prepare-release.yml` workflow: triggers on every push to main, computes the next version from fragments, creates/updates a `release-X.Y.Z` branch with towncrier-built changelog, and opens a PR. - Modify `test.yml`: set SETUPTOOLS_SCM_PRETEND_VERSION on release PRs so built packages have the exact release version. Add a `release-artifacts` job that creates/updates a draft GitHub release with the tested wheel+sdist after all checks pass. - Replace `deploy.yml`: trigger on `release: published` instead of tag push. Downloads bit-identical assets from the draft release, verifies PR checks, uploads to PyPI via trusted publishing, merges the PR, and cleans up the release branch. - Reclassify `632.removal.rst` as `632.feature.rst` (it's a deprecation warning, not a breaking removal). - Update RELEASING.rst and scripts/release.py to document and support the new automated flow (with manual fallback preserved). Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
The draft release is a staging area that gets continuously replaced as main evolves. Only a published (non-draft) release acts as a lock: - prepare-release: skips only when a published release exists for the version (meaning it is already on PyPI). Draft releases are freely overwritten by force-updating the release branch. - release-artifacts: creates or replaces the draft after every green test run. Skips only when a published release exists. To start a fresh release cycle after publishing, the next push to main with new fragments will compute a new version automatically. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
The `release-` prefix match was too broad — branches like `release-proposer` would set PRETEND_VERSION to `proposer`, breaking the build. Now validate that the suffix matches `^[0-9]+\.[0-9]+\.[0-9]+` before treating it as a version. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Automates the pluggy release process by deriving versions from Towncrier fragment types (via setuptools-scm’s towncrier-fragments scheme) and wiring GitHub Actions workflows to prepare release PRs, publish draft releases, and deploy to PyPI on release publication.
Changes:
- Add
scripts/compute_version.pyand updatepyproject.tomlto compute/derive versions from Towncrier fragments usingtowncrier-fragments. - Replace tag-push deployment with a release-publication-driven pipeline (
prepare-release.yml,release-artifactsjob intest.yml, and updateddeploy.yml). - Update release docs and expand tracing tests to cover PluginManager hook-call tracing output.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
testing/test_tracer.py |
Adds tests validating PluginManager tracing output for hook calls (single and multiple impls). |
scripts/release.py |
Updates legacy local release script to support auto-derived versions and align with the new CI pipeline. |
scripts/compute_version.py |
New helper to compute the next release version using setuptools-scm + Towncrier fragments. |
RELEASING.rst |
Documents the automated release flow and keeps a manual fallback section. |
pyproject.toml |
Bumps setuptools-scm requirement and enables version_scheme = "towncrier-fragments". |
changelog/632.feature.rst |
Adds a changelog entry for hookspec self/@staticmethod deprecation warning behavior. |
.github/workflows/test.yml |
Injects release-branch version into package build and adds a job to update a draft GitHub release from CI artifacts. |
.github/workflows/prepare-release.yml |
New workflow to compute version on main, build changelog, force-update release-X.Y.Z branch, and create/update the release PR. |
.github/workflows/deploy.yml |
Switches deployment trigger to “release published”, verifies PR checks, publishes to PyPI, merges PR, and deletes the release branch. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+26
to
+33
| def compute_version_auto() -> str: | ||
| """Derive the next release version from changelog fragments.""" | ||
| from scripts.compute_version import compute_version | ||
|
|
||
| version = compute_version() | ||
| if version is None: | ||
| raise RuntimeError("No changelog fragments found — cannot determine version.") | ||
| return version |
Comment on lines
35
to
38
| - uses: hynek/build-and-inspect-python-package@fe0a0fb1925ca263d076ca4f2c13e93a6e92a33e # v2.17.0 | ||
| env: | ||
| SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PLUGGY: ${{ steps.release.outputs.version || '' }} | ||
|
|
Comment on lines
+114
to
+115
| for pr in $(gh pr list --head 'release-' --json number,headRefName \ | ||
| --jq '.[] | select(.headRefName != "'"$BRANCH"'") | .number'); do |
- Rename `changelog/` to `changelog.d/` to match the convention expected by the `towncrier-fragments` version scheme in vcs-versioning. This makes `python -m setuptools_scm --strip-dev` correctly detect fragment types and compute the release version. - Remove `scripts/compute_version.py` — the setuptools_scm CLI with `--strip-dev` replaces it entirely. - Update `prepare-release.yml` and `scripts/release.py` to use the CLI. - Add `setuptools-scm[toml]>=10.0` to the tox release env deps. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
- test.yml: Set SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PLUGGY via
GITHUB_ENV only when a valid release version is detected, avoiding
an empty-string env var that would break setuptools-scm on
non-release branches.
- prepare-release.yml: Fix stale release PR cleanup — `gh pr list
--head` does exact matching, so list all PRs and filter by
`startswith("release-")` in jq instead.
Co-authored-by: Cursor AI <ai@cursor.sh>
Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Use the typed Octokit REST API via actions/github-script@v9 for all GitHub API interactions in the release workflows. This eliminates fragile shell+jq piping (including the --head prefix-matching bug) and makes the logic more readable and maintainable. Shell scripts are kept only for git operations, Python tooling, and simple asset downloads where they remain the better fit. Converted steps: - prepare-release.yml: release-published check, branch up-to-date check, PR create/update/close-stale - test.yml: draft release create/update with asset management - deploy.yml: PR lookup + CI status verification, PR merge + branch cleanup Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
Release PRs should validate downstream compatibility before publishing, so trigger the downstream workflow unconditionally when the head branch starts with `release-`. Co-authored-by: Cursor AI <ai@cursor.sh> Co-authored-by: Anthropic Claude Opus 4 <claude@anthropic.com>
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.
Automated release workflow
This replaces the manual release process with a fully automated CI pipeline.
The version is derived from changelog fragment types using the
towncrier-fragmentsscheme fromsetuptools-scm>=10.0:feature → minor, bugfix → patch, removal → major.
How it works
Contributors add
changelog/<id>.<type>.rstfragments in PRs as usual.Every push to
maintriggersprepare-release.yml, which computes thenext version, creates a
release-X.Y.Zbranch with the towncrier-builtchangelog, and opens (or updates) a PR.
The test workflow runs on the release PR. After all checks pass,
release-artifactscreates a draft GitHub release with the builtwheel and sdist attached.
A maintainer reviews the PR and publishes the draft release.
Publishing triggers
deploy.yml, which downloads the bit-identicalartifacts from the draft, verifies PR checks, uploads to PyPI via
trusted publishing, merges the PR, and cleans up the branch.
Locking semantics
version entirely — the next push with new fragments starts a new cycle.
Known unsolved problem
When a new changelog fragment changes the computed version (e.g. a
removalfragment lands bumping from
1.7.0to2.0.0), the release branch namechanges from
release-1.7.0torelease-2.0.0. The old branch/PR/draftbecome stale. The workflow closes superseded PRs, but the old draft release
and branch are not automatically cleaned up. This needs a strategy — likely
deleting the old draft and branch when the version changes.