name: Release
on:
workflow_dispatch:
inputs:
version:
description: Version from Cargo.toml, without a v prefix
required: true
type: string
concurrency:
group: crates-io-release
cancel-in-progress: false
permissions:
contents: write
id-token: write
attestations: write
jobs:
release:
name: Publish and attest release
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: true
- uses: actions/setup-python@v6
with:
python-version: "3.13"
- uses: dtolnay/rust-toolchain@stable
- name: Verify release version and tag
env:
VERSION: ${{ inputs.version }}
GH_TOKEN: ${{ github.token }}
run: |
python3 - <<'PY'
import os
import pathlib
import tomllib
expected = os.environ["VERSION"]
versions = {
pathlib.Path("Cargo.toml"): tomllib.loads(pathlib.Path("Cargo.toml").read_text())["package"]["version"],
pathlib.Path("macros/Cargo.toml"): tomllib.loads(pathlib.Path("macros/Cargo.toml").read_text())["package"]["version"],
}
mismatched = {str(path): version for path, version in versions.items() if version != expected}
if mismatched:
raise SystemExit(f"version mismatch: expected {expected}, found {mismatched}")
PY
git diff --exit-code
if gh release view "v${VERSION}" >/dev/null 2>&1; then
echo "release v${VERSION} already exists" >&2
exit 1
fi
- name: Prepare API baseline
env:
BASELINE_REF: HEAD^
run: |
mkdir .semver-baseline
git archive "${BASELINE_REF}" | tar -x -C .semver-baseline
python3 scripts/prepare_semver_baseline.py .semver-baseline
- name: Enforce public API compatibility
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
package: stateforward-sml
feature-group: all-features
baseline-root: .semver-baseline
- name: Check crates.io release state
id: registry
env:
VERSION: ${{ inputs.version }}
run: |
if cargo info "stateforward-sml-macros@${VERSION}" >/dev/null 2>&1; then
echo "macros_exists=true" >> "${GITHUB_OUTPUT}"
else
echo "macros_exists=false" >> "${GITHUB_OUTPUT}"
fi
if cargo info "stateforward-sml@${VERSION}" >/dev/null 2>&1; then
echo "runtime_exists=true" >> "${GITHUB_OUTPUT}"
else
echo "runtime_exists=false" >> "${GITHUB_OUTPUT}"
fi
- name: Validate crates.io release state
env:
VERSION: ${{ inputs.version }}
MACROS_EXISTS: ${{ steps.registry.outputs.macros_exists }}
RUNTIME_EXISTS: ${{ steps.registry.outputs.runtime_exists }}
run: |
if [ "${MACROS_EXISTS}" != "${RUNTIME_EXISTS}" ]; then
echo "Only one crate exists at ${VERSION}; finish the interrupted publication manually." >&2
exit 1
fi
if [ "${VERSION}" = "1.0.0" ] && [ "${MACROS_EXISTS}" = "false" ]; then
echo "Bootstrap 1.0.0 with an API token as documented in docs/releasing.md." >&2
exit 1
fi
- name: Package macro crate
run: cargo package -p stateforward-sml-macros
- name: Authenticate to crates.io
if: steps.registry.outputs.macros_exists == 'false'
uses: rust-lang/crates-io-auth-action@v1
id: auth
- name: Publish macro crate first
if: steps.registry.outputs.macros_exists == 'false'
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p stateforward-sml-macros
- name: Wait for macro crate index propagation
if: steps.registry.outputs.macros_exists == 'false'
env:
VERSION: ${{ inputs.version }}
run: |
for _ in $(seq 1 30); do
if cargo info "stateforward-sml-macros@${VERSION}" >/dev/null 2>&1; then
exit 0
fi
sleep 10
done
echo "stateforward-sml-macros ${VERSION} did not reach the crates.io index" >&2
exit 1
- name: Package runtime crate
run: cargo package -p stateforward-sml
- name: Publish runtime crate
if: steps.registry.outputs.runtime_exists == 'false'
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p stateforward-sml
- name: Attest published crate archives
uses: actions/attest-build-provenance@v4
with:
subject-path: target/package/*.crate
- name: Create annotated source tag and GitHub release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "v${VERSION}" -m "sml.rs ${VERSION}"
git push origin "v${VERSION}"
gh release create "v${VERSION}" \
target/package/*.crate \
--target "${GITHUB_SHA}" \
--title "sml.rs ${VERSION}" \
--generate-notes \
--verify-tag