Skip to content

build: adopt cargo-anvil check catalog (github backend) #3916

build: adopt cargo-anvil check catalog (github backend)

build: adopt cargo-anvil check catalog (github backend) #3916

Workflow file for this run

name: main
permissions:
contents: read
pull-requests: read
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
# Emits per-tier `--exclude NAME ...` args (modified/affected/required) to scope
# downstream `cargo --workspace` jobs. PRs snapshot base vs. the merge commit
# (same SHA downstream jobs build); pushes emit empty (full backstop).
# `skip=true` when no crates are impacted (e.g. doc-only PR).
delta:
runs-on: ubuntu-latest
outputs:
exclude_not_modified: ${{ steps.compute.outputs.exclude_not_modified }}
exclude_not_affected: ${{ steps.compute.outputs.exclude_not_affected }}
exclude_not_required: ${{ steps.compute.outputs.exclude_not_required }}
skip: ${{ steps.compute.outputs.skip }}
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: Setup
if: github.event_name == 'pull_request'
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_DELTA_VERSION
- name: Compute Impact
id: compute
shell: bash
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
# push to main (or any non-PR trigger) -> full backstop: no excludes.
{
echo "exclude_not_modified="
echo "exclude_not_affected="
echo "exclude_not_required="
echo "skip=false"
} >> "$GITHUB_OUTPUT"
exit 0
fi
# Snapshots written outside the working tree so `git checkout` can't trip on them.
baseline="$RUNNER_TEMP/delta-baseline.json"
current="$RUNNER_TEMP/delta-current.json"
head_sha=$(git rev-parse HEAD)
git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}"
git checkout "origin/$BASE_REF"
cargo delta -c .delta.toml snapshot > "$baseline"
git checkout "$head_sha"
cargo delta -c .delta.toml snapshot > "$current"
# `cargo-excludes` emits the workspace complement of the selected tier as
# `--exclude NAME ...`, scoped via downstream `cargo --workspace`. `--exclude`
# only matches workspace members, avoiding `-p` ambiguity with same-named
# registry deps.
impact() {
cargo delta -c .delta.toml impact --baseline "$baseline" --current "$current" "$@"
}
exclude_not_modified=$(impact -f cargo-excludes --modified)
exclude_not_affected=$(impact -f cargo-excludes --affected)
exclude_not_required=$(impact -f cargo-excludes --required)
# `required` is the superset (required ⊇ affected ⊇ modified). If it
# has no impacted crates, nothing is impacted and downstream
# source-scoped jobs can be skipped entirely.
if [[ -z "$(impact -f names --required)" ]]; then
skip=true
else
skip=false
fi
{
echo "exclude_not_modified=$exclude_not_modified"
echo "exclude_not_affected=$exclude_not_affected"
echo "exclude_not_required=$exclude_not_required"
echo "skip=$skip"
} | tee -a "$GITHUB_OUTPUT"
testing:
needs: [delta]
if: needs.delta.outputs.skip != 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm, windows-11-arm]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_MSRV, RUST_NIGHTLY
cargo-tools: CARGO_NEXTEST_VERSION, CARGO_HACK_VERSION, JUST_VERSION
# execute
- name: Build
run: cargo hack build --each-feature --workspace ${{ needs.delta.outputs.exclude_not_affected }} --verbose --locked --color always
- name: Tests
if: success() || failure()
run: cargo nextest run --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} --all-features
- name: Docs
if: success() || failure()
env:
RUSTDOCFLAGS: "--cfg docsrs -D warnings"
run: cargo +${{ env.RUST_NIGHTLY }} doc --no-deps --verbose --workspace ${{ needs.delta.outputs.exclude_not_required }} --all-features
- name: Doc Tests
if: success() || failure()
run: cargo test --doc --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} --all-features
- name: Doc Tests (default features)
if: success() || failure()
run: cargo test --doc --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }}
- name: Examples
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} -Zscript ./scripts/run-examples.rs --cargo-profile dev ${{ needs.delta.outputs.exclude_not_affected }}
static-analysis:
needs: [delta]
if: needs.delta.outputs.skip != 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm, windows-11-arm]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_LATEST
rust-components: clippy
cargo-tools: CARGO_DOC2README_VERSION, CARGO_WORKSPACES_VERSION, CARGO_ENSURE_NO_DEFAULT_FEATURES_VERSION, CARGO_ENSURE_NO_CYCLIC_DEPS_VERSION, CARGO_DENY_VERSION, CARGO_SORT_VERSION
# execute
- name: Clippy
run: cargo clippy --workspace ${{ needs.delta.outputs.exclude_not_modified }} --all-targets --all-features -- -D warnings
- name: Install Nightly Rustfmt
if: success() || failure()
run: rustup toolchain install ${{ env.RUST_NIGHTLY }} --profile minimal --component rustfmt
- name: Source Format (nightly)
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} fmt --all -- --config-path ./unstable-rustfmt.toml --check
- name: Cargo.toml Format
if: success() || failure()
run: cargo sort --check --grouped --workspace
- name: Readmes
if: success() || failure()
run: cargo workspaces exec --ignore-private cargo doc2readme --check --lib --template ../README.j2
env:
# Disable sccache: macro expansion (rustc -Zunpretty=expanded) does not
# produce the artifact files sccache expects to cache, causing it to fail.
RUSTC_WRAPPER: ""
- name: Default Features
if: success() || failure()
run: cargo ensure-no-default-features
- name: Cyclic Dependencies
if: success() || failure()
run: cargo ensure-no-cyclic-deps
- name: Dependency Validation
if: success() || failure()
run: cargo deny --all-features --workspace --color always check all
spell-check:
runs-on: ubuntu-slim
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 1
- name: Setup
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_SPELLCHECK_VERSION, JUST_VERSION
- name: Check Spelling
run: just spellcheck
semver:
needs: [delta]
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_SEMVER_CHECKS_VERSION
# execute
- name: Semver Compatibility
id: semver
continue-on-error: true
run: |
set -o pipefail
cargo semver-checks --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --color never --baseline-rev origin/${{ github.base_ref }} 2>&1 | tee semver-output.txt
# report
- name: Prepare Semver Comment
if: steps.semver.outcome == 'failure'
run: |
echo "## ⚠️ Breaking Changes Detected" > semver-comment.txt
echo "" >> semver-comment.txt
echo '```' >> semver-comment.txt
grep -v "^ *Cloning " semver-output.txt | grep -v "^ *Building " | grep -v "^ *Built \[" | grep -v "^ *Parsing " | grep -v "^ *Parsed \[" | grep -v "^ *Checking" | grep -v "^ *Checked \[" | grep -v "^ *Finished \[" | grep -v "^ *Summary " >> semver-comment.txt
echo '```' >> semver-comment.txt
echo "" >> semver-comment.txt
echo "If the breaking changes are intentional then everything is fine - this message is merely informative." >> semver-comment.txt
echo "" >> semver-comment.txt
echo "Remember to apply a version number bump with the correct severity when publishing a version with breaking changes (\`1.x.x -> 2.x.x\` or \`0.1.x -> 0.2.x\`)." >> semver-comment.txt
- name: Post Semver Failure Comment
if: steps.semver.outcome == 'failure' && github.event.pull_request.head.repo.full_name == github.repository
uses: marocchino/sticky-pull-request-comment@v3.0.4
with:
header: semver-check
path: semver-comment.txt
- name: Remove Semver Comment on Success
if: steps.semver.outcome == 'success' && github.event.pull_request.head.repo.full_name == github.repository
uses: marocchino/sticky-pull-request-comment@v3.0.4
with:
header: semver-check
delete: true
extended-analysis:
name: extended-analysis (${{ matrix.os }})
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
needs: [delta, testing, static-analysis]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: windows-11-arm
target: aarch64-pc-windows-msvc
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY
rust-components: miri
cargo-tools: CARGO_UDEPS_VERSION, CARGO_CAREFUL_VERSION, CARGO_NEXTEST_VERSION
# execute
- name: Udeps
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
# Without --all-targets: enforce that every [dependencies] entry is used
# by the library/bins (catches normal deps used only by tests/benches/
# examples -- i.e. misplaced deps that should be [dev-dependencies]).
cargo +${{ env.RUST_NIGHTLY }} udeps --locked --all-features --workspace ${{ needs.delta.outputs.exclude_not_modified }} --color always
# With --all-targets: enforce that every [dev-dependencies] entry is used
# by some target (catches unused dev-dependencies).
cargo +${{ env.RUST_NIGHTLY }} udeps --locked --all-features --all-targets --workspace ${{ needs.delta.outputs.exclude_not_modified }} --color always
- name: Careful
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} careful nextest run --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --color always --target ${{ matrix.target }}
- name: Miri (stacked borrows)
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} miri test --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --lib --tests
model-checking:
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
needs: [delta, testing]
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_LATEST
cargo-tools: JUST_VERSION
# execute
- name: Loom
run: just loom
fuzz-testing:
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
needs: [delta, testing]
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY
cargo-tools: CARGO_BOLERO_VERSION, JUST_VERSION
# execute
- name: Bolero
run: just bolero 60s
mutation-testing:
if: github.event_name == 'pull_request'
needs: [testing]
runs-on: ubuntu-latest
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
enable-wild-linker: true
rust-toolchain: RUST_NIGHTLY
cargo-tools: CARGO_MUTANTS_VERSION
# execute
- name: Generate PR Diff
run: git diff origin/main...HEAD >diff.txt
- name: Mutate Diff
timeout-minutes: 240
run: cargo +${{ env.RUST_NIGHTLY }} -Zscript scripts/mutants.rs --in-place --in-diff diff.txt
coverage:
needs: [delta]
# Coverage always runs (no `delta.skip` gate) so the externally-posted
# `codecov/project` status check is always reported. Codecov posts that
# status in reaction to a coverage upload from this job; if `coverage`
# is skipped, `codecov/project` stays in
# `Expected — Waiting for status to be reported` and blocks merge. The
# `required-checks` fan-in only solves stuck contexts for jobs we own,
# so we eat the cost of running coverage on doc-only PRs here to keep
# the externally-posted check unblocked.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Note: windows-11-arm excluded due to LLVM coverage instrumentation bugs
# causing "malformed instrumentation profile data: symbol name is empty" errors
# Note: when changing the number of OS entries here, update
# `codecov.notify.after_n_builds` in codecov.yml to match, otherwise
# Codecov reports the project status before all platforms have uploaded.
os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY
rust-components: llvm-tools-preview
cargo-tools: CARGO_LLVM_COV_VERSION
# execute
- name: Generate Coverage (all-features)
run: cargo +${{ env.RUST_NIGHTLY }} llvm-cov --all-features --workspace --lcov --output-path lcov-all.info
- name: Generate Coverage (no-default-features)
run: cargo +${{ env.RUST_NIGHTLY }} llvm-cov --no-default-features --workspace --lcov --output-path lcov-no-def.info
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov-all.info,lcov-no-def.info
fail_ci_if_error: true
pr-title:
runs-on: ubuntu-slim
if: github.event_name == 'pull_request'
steps:
- name: Check PR Title
uses: amannn/action-semantic-pull-request@v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
build
chore
ci
doc
docs
feat
fix
misc
miscellaneous
perf
refactor
style
task
test
license-headers:
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 1
- name: Setup
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_HEATHER_VERSION
- name: Check License Headers
run: cargo heather
external-type-exposure:
needs: [delta]
if: needs.delta.outputs.skip != 'true'
# Runs `cargo check-external-types --all-features` sequentially over every
# workspace crate, which exceeds the 15-minute `ubuntu-slim` execution cap as
# the crate/dependency graph grows. Use a standard runner with an explicit
# timeout so the job has headroom.
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY_EXTERNAL_TYPES
cargo-tools: CARGO_CHECK_EXTERNAL_TYPES_VERSION
# execute
- name: Check External Type Exposure
run: cargo +${{ env.RUST_NIGHTLY_EXTERNAL_TYPES }} -Zscript scripts/check-external-types.rs ${{ env.RUST_NIGHTLY_EXTERNAL_TYPES }}
script-tests:
# Pester test suite for the release-related PowerShell scripts
# (release-packages.ps1, lib/releasing.ps1, lib/release-flow.ps1).
# Always runs (no delta gate) because script-level changes don't move any
# Rust crates so delta would mark the PR skippable, but the scripts
# themselves may still need validation. Cross-platform matrix mirrors the
# platforms developers run the scripts on locally.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
# The synthetic-workspace fixture builder shells out to `cargo metadata`,
# so Rust must be available even though no Rust code is compiled.
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
- name: Install Pester
shell: pwsh
run: |
$existing = Get-Module Pester -ListAvailable |
Sort-Object Version -Descending |
Select-Object -First 1
if ($null -eq $existing -or $existing.Version -lt [version]'5.7.0') {
Install-Module -Name Pester -MinimumVersion 5.7.1 -Force -Scope CurrentUser -SkipPublisherCheck
} else {
Write-Host "Pester $($existing.Version) already installed; skipping."
}
# execute
- name: Run Pester Suite
shell: pwsh
run: ./scripts/tests/Pester/Run-Tests.ps1
# Single aggregating ("fan-in") status check. Branch protection should
# require ONLY this context for the workflow jobs below: it succeeds when
# every dependency either succeeded or was skipped, and fails if any
# dependency failed or was cancelled. This avoids the GitHub Actions
# quirk where a matrix job gated by a job-level `if:` posts only a single
# skipped check under the bare job name, leaving matrix-expanded required
# contexts (`testing (ubuntu-latest)` etc.) stuck on
# `Expected — Waiting for status to be reported`.
#
# Maintenance — when to add a job to `needs:` below:
#
# * Do NOT add jobs that are intentionally optional / advisory; this
# fan-in is only for jobs that must pass before merge.
#
# * MUST add if the job has BOTH a `strategy.matrix` AND a job-level
# `if:` that can evaluate to false (e.g. gated on
# `needs.delta.outputs.skip`, `github.event_name`, etc.). Such jobs
# trigger the stuck-context bug above and can only be made required
# safely via this fan-in.
#
# * SHOULD add any other job that must pass before merge. Funnelling
# every required job through this single context keeps branch
# protection simple: one workflow context to require here, plus
# externally-posted statuses (`license/cla`, `codecov/project`).
#
# When a new job is added below, also remove any direct entry for it
# from the branch protection ruleset (only `required-checks` should be
# listed for jobs defined in this workflow).
required-checks:
name: required-checks
if: always()
needs:
- delta
- testing
- static-analysis
- spell-check
- semver
- extended-analysis
- model-checking
- fuzz-testing
- mutation-testing
- coverage
- pr-title
- license-headers
- external-type-exposure
- script-tests
runs-on: ubuntu-latest
steps:
- name: Validate Dependency Results
shell: bash
env:
NEEDS_JSON: ${{ toJSON(needs) }}
run: |
set -euo pipefail
echo "$NEEDS_JSON" | jq -r 'to_entries[] | " \(.key): \(.value.result)"'
# Pass when every dependency is success or skipped; fail otherwise
# (failure, cancelled, or any unexpected state).
bad=$(echo "$NEEDS_JSON" | jq -r '
to_entries
| map(select(.value.result != "success" and .value.result != "skipped"))
| .[] | "\(.key): \(.value.result)"
')
if [[ -n "$bad" ]]; then
printf '::error::One or more required jobs did not succeed or skip:\n%s\n' "$bad"
exit 1
fi
echo "All required jobs succeeded or were skipped."