name: Publish Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release, without leading v (for example: 0.5.1)'
required: true
type: string
jobs:
prepare-release:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump versions, commit, and tag
id: release
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Version must be in X.Y.Z format, got: $VERSION" >&2
exit 1
fi
TAG="v${VERSION}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists" >&2
exit 1
fi
CURRENT_VERSION=$(sed -n '/^\[workspace\.package\]/,/^\[/{s/^version = "\(.*\)"/\1/p}' Cargo.toml)
sed -i '/^\[workspace\.package\]/,/^\[/{s/^version = ".*"/version = "'"$VERSION"'"/}' Cargo.toml
sed -i '/^\[workspace\.dependencies\]/,/^\[/{s/version = "'"$CURRENT_VERSION"'"/version = "'"$VERSION"'"/g}' Cargo.toml
sed -i '/^\[project\]/,/^\[/{s/^version = ".*"/version = "'"$VERSION"'"/}' pyproject.toml
# Update every Cargo.lock owned by the repo, including nested independent
# workspaces. A Cargo.lock belongs to the Cargo.toml in the same directory.
declare -A CARGO_MANIFESTS=()
CARGO_MANIFESTS["./Cargo.toml"]=1
while IFS= read -r -d '' manifest; do
dir="$(dirname "$manifest")"
# Include workspace roots, plus any manifest sitting next to a Cargo.lock.
# This catches nested independent workspaces and standalone Rust packages.
if grep -q '^[[:space:]]*\[workspace\]' "$manifest" || [ -f "$dir/Cargo.lock" ]; then
CARGO_MANIFESTS["$manifest"]=1
fi
done < <(
find . \
\( -path './.git' -o -path '*/target' \) -prune -o \
-name Cargo.toml -print0
)
for manifest in "${!CARGO_MANIFESTS[@]}"; do
echo "::group::cargo update --workspace --manifest-path ${manifest}"
cargo update --workspace --manifest-path "$manifest"
echo "::endgroup::"
done
docker run --rm -v "$PWD:/workspace" -w /workspace codeberg.org/msrd0/cargo-doc2readme cargo doc2readme
git add Cargo.toml pyproject.toml README.md
# Stage all lockfiles, not just ./Cargo.lock.
while IFS= read -r -d '' lockfile; do
git add "$lockfile"
done < <(
find . \
\( -path './.git' -o -path '*/target' \) -prune -o \
-name Cargo.lock -print0
)
# Safety check: fail before tagging if generated changes were missed.
if ! git diff --quiet; then
echo "Unstaged generated changes remain:" >&2
git status --short
exit 1
fi
if git diff --cached --quiet; then
echo "No version changes to commit" >&2
exit 1
fi
git commit -m "chore(release): prepare for ${TAG}"
git tag "$TAG"
git push origin "HEAD:${GITHUB_REF}"
git push origin "$TAG"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
build-python-linux:
needs: prepare-release
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64
- runner: ubuntu-22.04
target: x86
- runner: ubuntu-22.04
target: aarch64
- runner: ubuntu-22.04
target: armv7
- runner: ubuntu-22.04
target: s390x
- runner: ubuntu-22.04
target: ppc64le
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-release.outputs.tag }}
- uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --generate-stubs
sccache: true
manylinux: 2_28
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist
build-python-musllinux:
needs: prepare-release
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- runner: ubuntu-22.04
target: x86_64
- runner: ubuntu-22.04
target: x86
- runner: ubuntu-22.04
target: aarch64
- runner: ubuntu-22.04
target: armv7
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-release.outputs.tag }}
- uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --generate-stubs
sccache: true
manylinux: musllinux_1_2
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: dist
build-python-windows:
needs: prepare-release
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- runner: windows-latest
target: x64
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-release.outputs.tag }}
- uses: actions/setup-python@v6
with:
python-version: 3.x
architecture: ${{ matrix.platform.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --generate-stubs
sccache: true
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist
build-python-macos:
needs: prepare-release
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- runner: macos-14
target: aarch64
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-release.outputs.tag }}
- uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --generate-stubs
sccache: true
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist
build-python-sdist:
needs: prepare-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare-release.outputs.tag }}
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist
publish-rust:
needs: prepare-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare-release.outputs.tag }}
- name: Install cargo-workspaces
run: cargo install cargo-workspaces
- name: Publish crates
run: cargo workspaces publish --from-git --yes --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
release:
runs-on: ubuntu-latest
needs: [ prepare-release, build-python-linux, build-python-musllinux, build-python-windows, build-python-macos, build-python-sdist, publish-rust ]
permissions:
id-token: write
contents: write
attestations: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare-release.outputs.tag }}
fetch-depth: 0
- uses: actions/download-artifact@v4
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-path: 'wheels-*/*'
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*
- name: Generate changelog
id: changelog
uses: orhun/git-cliff-action@v4
with:
args: --latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare-release.outputs.tag }}
body: ${{ steps.changelog.outputs.content }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}