Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/scripts/check_release_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# /// script
# dependencies = ["packaging"]
# ///
"""Validate RELEASE_VERSION against RELEASE_TAGS."""

import contextlib
import os
import sys

from packaging.version import InvalidVersion, Version

release = Version(os.environ["RELEASE_VERSION"])
if release.local is not None:
sys.exit(f"v{release} is a local version and cannot be published to PyPI")

versions = []
for tag in os.environ["RELEASE_TAGS"].split():
with contextlib.suppress(InvalidVersion):
versions.append(Version(tag))

if release in versions:
sys.exit(f"v{release} has already been released")

latest = max(versions, default=None)
if latest is not None and release < latest:
sys.exit(f"v{release} is not newer than the latest release, v{latest}")
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
pull_request:
merge_group:

permissions: {}

Expand Down
93 changes: 93 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: prepare release

on:
workflow_dispatch:
inputs:
version:
description: Version to release; leave blank to bump the minor version
required: false
type: string

permissions: {}

concurrency:
group: prepare-release
cancel-in-progress: false

jobs:
prepare:
name: Create release branch
runs-on: ubuntu-latest
permissions:
contents: write
env:
REQUESTED_VERSION: ${{ inputs.version }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: main

- name: Install uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: false

- name: Bump version
run: |
if test -n "$REQUESTED_VERSION"; then
uv version "$REQUESTED_VERSION" --no-sync
else
uv version --bump minor --no-sync
fi

release_version=$(uv version --short)
release_tags=$(git ls-remote --tags origin 'v*' | cut -f2 | sed -e 's|^refs/tags/||' -e '/\^{}$/d')
RELEASE_VERSION="$release_version" RELEASE_TAGS="$release_tags" uv run .github/scripts/check_release_version.py

if git diff --quiet -- pyproject.toml uv.lock; then
echo "v$release_version does not change the version files" >&2
exit 1
fi

echo "RELEASE_VERSION=$release_version" >> "$GITHUB_ENV"

- name: Create release branch
id: commit
env:
GH_TOKEN: ${{ github.token }}
run: |
release_branch="release/v$RELEASE_VERSION"

if git ls-remote --exit-code origin "refs/heads/$release_branch" >/dev/null; then
echo "$release_branch already exists; delete it before preparing this release again" >&2
exit 1
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add pyproject.toml uv.lock
git commit -m "Prepare release v$RELEASE_VERSION"

release_commit=$(git rev-parse HEAD)
gh auth setup-git
git push origin "HEAD:refs/heads/$release_branch"

echo "branch=$release_branch" >> "$GITHUB_OUTPUT"
echo "commit=$release_commit" >> "$GITHUB_OUTPUT"

- name: Show pull request link
env:
RELEASE_BRANCH: ${{ steps.commit.outputs.branch }}
RELEASE_COMMIT: ${{ steps.commit.outputs.commit }}
run: |
{
echo "## Release v$RELEASE_VERSION is ready"
echo
echo "Commit: \`$RELEASE_COMMIT\`"
echo
echo "[Open the release pull request](https://github.com/$GITHUB_REPOSITORY/compare/main...$RELEASE_BRANCH?expand=1)"
echo
echo "Merging this pull request will publish the release automatically."
} >> "$GITHUB_STEP_SUMMARY"
161 changes: 161 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: publish release

on:
pull_request:
types:
- closed
branches:
- main

permissions: {}

# Serialize release PRs in FIFO order without letting no-op runs block them.
concurrency:
group: ${{ github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') && 'release' || format('release-noop-{0}', github.run_id) }}
cancel-in-progress: false
queue: max

jobs:
build:
name: Build and tag distributions
if: >-
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.event.pull_request.head.repo.full_name == github.repository &&
startsWith(github.event.pull_request.head.ref, 'release/')
runs-on: ubuntu-latest
outputs:
release-version: ${{ steps.version.outputs.release-version }}
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event.pull_request.merge_commit_sha }}

- name: Install uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: false

- name: Read version
id: version
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
release_version=$(uv version --short)
release_tag="v$release_version"

if test "$HEAD_REF" != "release/$release_tag"; then
echo "$HEAD_REF does not match the version being released, $release_tag" >&2
exit 1
fi

# Skip version-order validation when rerunning this tagged commit.
if test "$(git rev-parse --verify --quiet "refs/tags/$release_tag^{commit}" || true)" != "$(git rev-parse HEAD)"; then
release_tags=$(git tag --list 'v*')
RELEASE_VERSION="$release_version" RELEASE_TAGS="$release_tags" uv run .github/scripts/check_release_version.py
fi

echo "RELEASE_VERSION=$release_version" >> "$GITHUB_ENV"
echo "release-version=$release_version" >> "$GITHUB_OUTPUT"

- name: Build distributions
run: uv build

# Let build-job reruns replace the previous artifact.
- name: Upload distributions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: distributions
path: dist/
overwrite: true

# Preserve a tag on this commit; a plain push rejects remote conflicts.
- name: Push release tag
env:
GH_TOKEN: ${{ github.token }}
run: |
tag_commit=$(git rev-parse --verify --quiet "refs/tags/v$RELEASE_VERSION^{commit}" || true)
if test "$tag_commit" != "$(git rev-parse HEAD)"; then
git tag -f "v$RELEASE_VERSION"
fi
gh auth setup-git
git push origin "refs/tags/v$RELEASE_VERSION"

generate-provenance:
name: Generate build provenance
runs-on: ubuntu-latest
needs: build
permissions:
id-token: write
attestations: write
steps:
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: distributions
path: dist/

- name: Attest distributions
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: dist/*

release:
name: Create GitHub release
runs-on: ubuntu-latest
needs:
- build
- generate-provenance
permissions:
contents: write
steps:
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: distributions
path: dist/

- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_VERSION: ${{ needs.build.outputs.release-version }}
run: |
# Keep existing release assets unchanged on reruns.
if ! gh release view "v$RELEASE_VERSION" >/dev/null 2>&1; then
gh release create "v$RELEASE_VERSION" dist/* --generate-notes --verify-tag
fi

publish:
name: Publish distributions to PyPI
runs-on: ubuntu-latest
needs:
- build
- generate-provenance
- release
environment:
name: pypi
url: https://pypi.org/p/idac
permissions:
contents: read
id-token: write
steps:
# Reuse release assets so partial PyPI uploads resume with identical bytes.
- name: Download release assets
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_VERSION: ${{ needs.build.outputs.release-version }}
run: gh release download "v$RELEASE_VERSION" --dir dist/

- name: Publish distributions
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
attestations: true
# Ignore files already published by a partial run.
skip-existing: true
1 change: 1 addition & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: ["main"]
pull_request:
branches: ["**"]
merge_group:

permissions: {}

Expand Down
Loading
Loading