name: Release
on:
push:
tags:
- "v*"
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
verify-version:
name: Verify tag matches version
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Check tag against Cargo.toml version
run: |
tag="${GITHUB_REF_NAME#v}"
manifest="$(grep -m1 '^version = ' Cargo.toml | sed -E 's/version = "(.*)"/\1/')"
echo "tag version: $tag"
echo "manifest version: $manifest"
if [ "$tag" != "$manifest" ]; then
echo "::error::tag $tag does not match Cargo.toml version $manifest"
exit 1
fi
ci-green:
name: CI is green on tagged commit
runs-on: ubuntu-latest
needs: verify-version
permissions:
contents: read
actions: read
steps:
- name: Wait for CI to finish on the tagged commit
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: |
# `github.sha` for a tag push is the commit the tag points at, which
# is the same commit CI runs against on `push: branches: [main]`.
# Poll `workflow_runs?head_sha=$SHA` until the run for this commit is
# complete, then require its conclusion to be `success`. Using a URL
# query string keeps this a GET. Passing the same keys via
# `gh api --field` turns it into a POST, which the endpoint answers
# with 404.
attempts=0
max_attempts=60 # 60 * 30s = 30 minutes
sleep_seconds=30
while :; do
run="$(gh api \
"repos/${GITHUB_REPOSITORY}/actions/workflows/ci.yml/runs?head_sha=${SHA}&per_page=10" \
--jq '[.workflow_runs[] | select(.event == "push" or .event == "workflow_dispatch")][0] // {}')"
status="$(printf '%s' "$run" | jq -r '.status // "missing"')"
conclusion="$(printf '%s' "$run" | jq -r '.conclusion // "null"')"
echo "CI run for ${SHA}: status='${status}' conclusion='${conclusion}'"
if [ "$status" = "completed" ]; then
break
fi
attempts=$((attempts + 1))
if [ "$attempts" -ge "$max_attempts" ]; then
echo "::error::Timed out waiting for CI on ${SHA} to finish (status='${status}')."
exit 1
fi
sleep "$sleep_seconds"
done
if [ "$conclusion" != "success" ]; then
echo "::error::CI for ${SHA} has conclusion '${conclusion}'. CI must be green before releasing."
exit 1
fi
echo "CI for ${SHA} is green."
publish:
name: Publish to crates.io
needs: ci-green
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
- name: Authenticate to crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish crate
run: cargo publish --locked
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}