name: Release
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to attach artifacts to (e.g., v0.1.6)'
required: true
type: string
jobs:
validate:
name: Validate Release
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev pkg-config
- name: Install cargo-make
uses: davidB/rust-cargo-make@v1
- name: Install cargo-audit
uses: taiki-e/install-action@v2
with:
tool: cargo-audit
- name: Install cargo-deny
uses: taiki-e/install-action@v2
with:
tool: cargo-deny
- name: Verify Tag Matches Cargo.toml Version
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
VERSION="${{ github.ref_name }}"
VERSION_CLEAN="${VERSION#v}"
VERSION_BASE="${VERSION_CLEAN%%-*}"
TOML_VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n 1)"
if [[ -z "$TOML_VERSION" ]]; then
echo "Failed to read Cargo.toml version."
exit 1
fi
EXPECT_VERSION="$VERSION_CLEAN"
if [[ "$VERSION_CLEAN" == *"-"* ]]; then
EXPECT_VERSION="$VERSION_BASE"
fi
if [[ "$TOML_VERSION" != "$EXPECT_VERSION" ]]; then
echo "Tag version ($VERSION_CLEAN) does not match Cargo.toml ($TOML_VERSION)."
exit 1
fi
- name: Verify Changelog Section
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
VERSION="${{ github.ref_name }}"
VERSION_CLEAN="${VERSION#v}"
VERSION_BASE="${VERSION_CLEAN%%-*}"
if ! grep -qE "^## ${VERSION_BASE}$" CHANGELOG.md; then
echo "Missing CHANGELOG.md section for ${VERSION_BASE}."
exit 1
fi
- name: Release Tag Info
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
VERSION="${{ github.ref_name }}"
VERSION_CLEAN="${VERSION#v}"
VERSION_BASE="${VERSION_CLEAN%%-*}"
if [[ "$VERSION_CLEAN" == *"-"* ]]; then
echo "Detected pre-release tag: $VERSION" >> "$GITHUB_STEP_SUMMARY"
echo "Using base version for changelog/Cargo.toml checks: $VERSION_BASE" >> "$GITHUB_STEP_SUMMARY"
echo "Publish/build jobs are skipped for prerelease tags." >> "$GITHUB_STEP_SUMMARY"
else
echo "Detected release tag: $VERSION" >> "$GITHUB_STEP_SUMMARY"
echo "Publish/build jobs will run." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Audit Dependencies
run: cargo make audit
- name: Deny Check
run: cargo make deny
- name: Run Tests
run: cargo make test
- name: Run WASM Tests
run: cargo make test-wasm
- name: Lint
run: cargo make clippy
- name: Format Check
run: cargo make format-check
- name: Architecture Guardrails
run: cargo make architecture-check
- name: Verify Publish Readiness
run: cargo publish --dry-run --locked
- name: Package Check
run: cargo package --locked
publish:
if: github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
name: Publish to Crates.io
needs: validate
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
changelog: ${{ steps.changelog_reader.outputs.changes }}
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev pkg-config
- name: Extract Changelog Section
id: changelog_reader
run: |
VERSION="${{ github.ref_name }}"
VERSION_CLEAN="${VERSION#v}"
VERSION_BASE="${VERSION_CLEAN%%-*}"
CHANGES=$(sed -n "/^## $VERSION_BASE/,/^## /p" CHANGELOG.md | sed '1d;$d')
echo "changes<<EOF" >> "$GITHUB_OUTPUT"
echo "$CHANGES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- uses: dtolnay/rust-toolchain@stable
- name: Ensure Changelog Has Notes
shell: bash
run: |
if [[ -z "${{ steps.changelog_reader.outputs.changes }}" ]]; then
echo "Changelog section is empty for ${{ github.ref_name }}."
exit 1
fi
- name: Publish to Cargo
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --locked
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body: |
### 🚀 What's New
${{ steps.changelog_reader.outputs.changes }}
### 📦 Installation
```bash
cargo install strest
```
makeLatest: true
build-binaries:
name: Build Binaries
needs: publish
if: (github.event_name == 'workflow_dispatch') || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-'))
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
suffix: linux-x86_64
archive: tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
suffix: windows-x86_64
archive: zip
- os: macos-latest
target: x86_64-apple-darwin
suffix: macos-x86_64
archive: tar.gz
- os: macos-latest
target: aarch64-apple-darwin
suffix: macos-arm64
archive: tar.gz
rust_min_stack: 16777216
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install system dependencies (Linux)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev pkg-config
- name: Build Release
env:
RUST_MIN_STACK: ${{ matrix.rust_min_stack }}
run: cargo build --release --target ${{ matrix.target }} --locked
- name: Package Assets
shell: bash
run: |
RELEASE_TAG="${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}"
mkdir dist
# Determine binary name
BIN_NAME="strest"
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then BIN_NAME="strest.exe"; fi
# Copy binary and metadata
cp "target/${{ matrix.target }}/release/$BIN_NAME" dist/
cp README.md LICENSE dist/ 2>/dev/null || :
# Create Archive
cd dist
if [[ "${{ matrix.archive }}" == "tar.gz" ]]; then
tar -czf "../strest-${RELEASE_TAG}-${{ matrix.suffix }}.tar.gz" *
else
7z a "../strest-${RELEASE_TAG}-${{ matrix.suffix }}.zip" *
fi
- name: Upload to Release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: strest-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}-${{ matrix.suffix }}.${{ matrix.archive }}
asset_name: strest-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}-${{ matrix.suffix }}.${{ matrix.archive }}
tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}