name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
validate:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
tag_name: ${{ steps.version.outputs.tag_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read and validate version
id: version
run: |
CARGO_VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
LOCK_VERSION="$(
awk '
$0 == "[[package]]" { in_pkg = 0; next }
$0 == "name = \"anitrack\"" { in_pkg = 1; next }
in_pkg && $1 == "version" {
gsub(/"/, "", $3)
print $3
exit
}
' Cargo.lock
)"
TAG_NAME="${GITHUB_REF_NAME}"
TAG_VERSION="${TAG_NAME#v}"
if [ -z "${CARGO_VERSION}" ]; then
echo "Failed to read version from Cargo.toml"
exit 1
fi
if [ -z "${LOCK_VERSION}" ]; then
echo "Failed to read anitrack version from Cargo.lock"
exit 1
fi
if [ "${CARGO_VERSION}" != "${LOCK_VERSION}" ]; then
echo "Cargo.toml version (${CARGO_VERSION}) does not match Cargo.lock (${LOCK_VERSION})"
echo "Run cargo test --all-features locally to refresh Cargo.lock, commit it, then retag."
exit 1
fi
if [ "${CARGO_VERSION}" != "${TAG_VERSION}" ]; then
echo "Tag version (${TAG_VERSION}) does not match Cargo.toml (${CARGO_VERSION})"
exit 1
fi
echo "version=${CARGO_VERSION}" >> "${GITHUB_OUTPUT}"
echo "tag_name=${TAG_NAME}" >> "${GITHUB_OUTPUT}"
- name: Verify changelog entry exists
run: |
VERSION="${{ steps.version.outputs.version }}"
if ! grep -q "^## \\[${VERSION}\\]" CHANGELOG.md; then
echo "Missing CHANGELOG.md section for version ${VERSION}"
echo "Expected header format: ## [${VERSION}] - YYYY-MM-DD"
exit 1
fi
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Run tests
run: cargo test --all-features --locked
build-linux-x86_64:
runs-on: ubuntu-latest
needs: validate
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Build release binary
run: cargo build --release --locked
- name: Package artifacts
run: |
VERSION="${{ needs.validate.outputs.version }}"
ASSET_BASE="anitrack-v${VERSION}-x86_64-unknown-linux-gnu"
mkdir -p dist
cp target/release/anitrack dist/anitrack
tar -C dist -czf "${ASSET_BASE}.tar.gz" anitrack
sha256sum "${ASSET_BASE}.tar.gz" > "${ASSET_BASE}.tar.gz.sha256"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-assets
path: |
anitrack-v${{ needs.validate.outputs.version }}-x86_64-unknown-linux-gnu.tar.gz
anitrack-v${{ needs.validate.outputs.version }}-x86_64-unknown-linux-gnu.tar.gz.sha256
github-release:
runs-on: ubuntu-latest
needs: [validate, build-linux-x86_64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: release-assets
path: release-assets
- name: Extract release notes from changelog
run: |
VERSION="${{ needs.validate.outputs.version }}"
awk -v version="${VERSION}" '
$0 ~ "^## \\[" version "\\]" { in_section = 1; next }
in_section && $0 ~ "^## \\[" { exit }
in_section { print }
' CHANGELOG.md > release-notes.md
# Trim leading blank lines produced after section header removal.
sed -i '/./,$!d' release-notes.md
if ! grep -q '[^[:space:]]' release-notes.md; then
echo "Failed to extract release notes for ${VERSION} from CHANGELOG.md"
exit 1
fi
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.validate.outputs.tag_name }}
body_path: release-notes.md
files: release-assets/*
publish-crates:
runs-on: ubuntu-latest
needs: [validate, build-linux-x86_64, github-release]
environment: release
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Authenticate with crates.io (OIDC)
id: crates-auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish to crates.io
run: cargo publish --locked
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
aur-summary:
runs-on: ubuntu-latest
needs: [validate, build-linux-x86_64]
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: release-assets
path: release-assets
- name: Write AUR update summary
run: |
VERSION="${{ needs.validate.outputs.version }}"
TAG="${{ needs.validate.outputs.tag_name }}"
ASSET="anitrack-v${VERSION}-x86_64-unknown-linux-gnu.tar.gz"
SHA256="$(cut -d' ' -f1 "release-assets/${ASSET}.sha256")"
URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${ASSET}"
{
echo "## AUR update values"
echo ""
echo "### anitrack-bin (prebuilt)"
echo "- pkgver: \`${VERSION}\`"
echo "- source URL: \`${URL}\`"
echo "- sha256sums: \`${SHA256}\`"
echo ""
echo "### anitrack (source build)"
echo "- pkgver: \`${VERSION}\`"
echo "- source: \`https://crates.io/api/v1/crates/anitrack/${VERSION}/download\`"
echo "- note: if you use pinned checksums, run \`updpkgsums\`."
echo ""
echo "Regenerate \`.SRCINFO\` and push both AUR repos."
} >> "${GITHUB_STEP_SUMMARY}"