From d92035f079f0b7baff863ddd1eff6c013f9840cd Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Sun, 20 Sep 2026 11:22:16 -0700 Subject: [PATCH 1/2] Fix release publishing and protected auto-merge --- .github/workflows/version.yml | 30 +++++++++++++++++++----------- docs/changelog.md | 3 +++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 2d452a9e..8166b9fe 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -6,7 +6,7 @@ # 5. Pulls that branch and builds it for different platforms # 6. Once all those build, makes a release # 7. Creates a tag for that release -# 7. merges that PR when tests pass and removes the branch +# 8. queues the PR to merge when required checks pass and removes the branch # This seperates the release process from the test process, so we can still release if we need to and tests are failing name: Bump Version @@ -46,7 +46,9 @@ jobs: git checkout -b "version-$VERSION" git commit -am "Version $VERSION" git push -u origin HEAD - gh pr create --fill + PR_URL=$(gh pr create --fill) + # GITHUB_TOKEN-created PRs need approval before their checks run. + echo "Release PR: $PR_URL. Approve both the CI and Bump Version workflows on the PR to allow auto-merge." >> "$GITHUB_STEP_SUMMARY" echo "version=$VERSION" >> "$GITHUB_OUTPUT" id: bump env: @@ -149,11 +151,16 @@ jobs: - name: Set up supported Python interpreters uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: + # Keep regular 3.14 last so it takes precedence over the free-threaded alias. python-version: | 3.12 3.13 - 3.14 3.14t + 3.14 + - name: Verify Python 3.14 variants + run: | + python3.14 -c 'import sysconfig; assert not sysconfig.get_config_var("Py_GIL_DISABLED")' + python3.14t -c 'import sysconfig; assert sysconfig.get_config_var("Py_GIL_DISABLED") == 1' - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: command: build @@ -175,13 +182,14 @@ jobs: with: pattern: wheels-* merge-multiple: true + path: dist + - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 + with: + enable-cache: false - name: Publish to PyPI - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + run: uv publish env: - MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - with: - command: upload - args: --skip-existing * + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} tag-and-merge: runs-on: ubuntu-latest permissions: write-all @@ -191,9 +199,9 @@ jobs: with: ref: version-${{ needs.bump.outputs.version }} - run: | - git tag "v$VERSION" - git push --tags - gh pr merge --delete-branch --merge --admin + # Retrying accepts the same tag, but never overwrites a different commit. + git push origin "HEAD:refs/tags/v$VERSION" + gh pr merge "version-$VERSION" --delete-branch --merge --auto --match-head-commit "$(git rev-parse HEAD)" env: VERSION: ${{ needs.bump.outputs.version }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/changelog.md b/docs/changelog.md index f409f967..b49a3a51 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,9 @@ _This project uses semantic versioning_ ## UNRELEASED +- Publish releases with `uv`, make tag retries safe, and queue release PRs to merge after required checks pass. +- Fix missing macOS wheels for standard CPython 3.14 when building alongside free-threaded Python. + ## 14.0.0 (2026-09-20) - Fix release version bumps to update `Cargo.lock` before building locked wheels. From fa5663b7a8501fa12c618a2055520373b48fa94a Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Sun, 20 Sep 2026 11:39:01 -0700 Subject: [PATCH 2/2] Push published releases directly to main --- .github/workflows/version.yml | 64 +++++++++++++++++++++++------------ docs/changelog.md | 2 +- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 8166b9fe..6650aa8b 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -2,11 +2,9 @@ # 1. bumping the version number in the changelog and cargo # 2. Creates a new branch for this version # 3. makes a new commit -# 4. Opens a PR for the new version -# 5. Pulls that branch and builds it for different platforms -# 6. Once all those build, makes a release -# 7. Creates a tag for that release -# 8. queues the PR to merge when required checks pass and removes the branch +# 4. Builds that commit for different platforms +# 5. Once all those build, publishes to PyPI +# 6. Creates a tag and fast-forwards main to the release commit # This seperates the release process from the test process, so we can still release if we need to and tests are failing name: Bump Version @@ -31,29 +29,37 @@ jobs: bump: runs-on: ubuntu-latest permissions: write-all - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' outputs: version: ${{ steps.bump.outputs.version }} + base: ${{ steps.bump.outputs.base }} + commit: ${{ steps.bump.outputs.commit }} steps: + - name: Check release push token + run: | + if [ -z "$RELEASE_GITHUB_TOKEN" ]; then + echo "::error::Set RELEASE_GITHUB_TOKEN to a token whose actor can bypass main's pull-request and required-check rules." + exit 1 + fi + env: + RELEASE_GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@ebb3d1676050bfd0971c36c1e215b5751473994d # 1.96.0 - run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com + echo "base=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" VERSION=$(python modify_changelog.py bump_version "$TYPE") # Update the workspace version without changing dependency pins. cargo update --workspace git checkout -b "version-$VERSION" - git commit -am "Version $VERSION" + git commit -am "Version $VERSION [skip ci]" git push -u origin HEAD - PR_URL=$(gh pr create --fill) - # GITHUB_TOKEN-created PRs need approval before their checks run. - echo "Release PR: $PR_URL. Approve both the CI and Bump Version workflows on the PR to allow auto-merge." >> "$GITHUB_STEP_SUMMARY" echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" id: bump env: TYPE: ${{ inputs.type }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux-cross: name: build linux @@ -67,7 +73,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'success' }} with: - ref: version-${{ needs.bump.outputs.version }} + ref: ${{ needs.bump.outputs.commit }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'skipped' }} - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 @@ -91,7 +97,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'success' }} with: - ref: version-${{ needs.bump.outputs.version }} + ref: ${{ needs.bump.outputs.commit }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'skipped' }} - name: Setup QEMU @@ -120,7 +126,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'success' }} with: - ref: version-${{ needs.bump.outputs.version }} + ref: ${{ needs.bump.outputs.commit }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'skipped' }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 @@ -145,7 +151,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'success' }} with: - ref: version-${{ needs.bump.outputs.version }} + ref: ${{ needs.bump.outputs.commit }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ needs.bump.result == 'skipped' }} - name: Set up supported Python interpreters @@ -175,9 +181,12 @@ jobs: release: name: Release runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch') + if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' needs: [macos, windows, linux, linux-cross, bump] steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.bump.outputs.commit }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: wheels-* @@ -186,22 +195,33 @@ jobs: - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 with: enable-cache: false + - name: Check main before publishing + run: | + MAIN=$(git ls-remote origin refs/heads/main | cut -f1) + if [ "$MAIN" != "$BASE_SHA" ] && [ "$MAIN" != "$RELEASE_SHA" ]; then + echo "::error::main changed since this release was prepared." + exit 1 + fi + env: + BASE_SHA: ${{ needs.bump.outputs.base }} + RELEASE_SHA: ${{ needs.bump.outputs.commit }} - name: Publish to PyPI run: uv publish env: UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - tag-and-merge: + tag-and-push: runs-on: ubuntu-latest permissions: write-all needs: [release, bump] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: version-${{ needs.bump.outputs.version }} + ref: ${{ needs.bump.outputs.commit }} + token: ${{ secrets.RELEASE_GITHUB_TOKEN }} + fetch-depth: 0 - run: | - # Retrying accepts the same tag, but never overwrites a different commit. - git push origin "HEAD:refs/tags/v$VERSION" - gh pr merge "version-$VERSION" --delete-branch --merge --auto --match-head-commit "$(git rev-parse HEAD)" + # Neither ref changes if main advanced or the tag points to a different commit. + git push --atomic origin "$RELEASE_SHA:refs/heads/main" "$RELEASE_SHA:refs/tags/v$VERSION" env: VERSION: ${{ needs.bump.outputs.version }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_SHA: ${{ needs.bump.outputs.commit }} diff --git a/docs/changelog.md b/docs/changelog.md index b49a3a51..52705476 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,7 +4,7 @@ _This project uses semantic versioning_ ## UNRELEASED -- Publish releases with `uv`, make tag retries safe, and queue release PRs to merge after required checks pass. +- Publish releases with `uv`, then tag and fast-forward `main` directly without a release PR or redundant CI. - Fix missing macOS wheels for standard CPython 3.14 when building alongside free-threaded Python. ## 14.0.0 (2026-09-20)