name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
rust: ${{ steps.filter.outputs.rust }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check for non-docs changes
id: filter
env:
EVENT: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
run: |
if [ "$EVENT" = pull_request ] &&
git diff --name-only "origin/$BASE_REF...HEAD" | bash scripts/docs-only-change; then
echo "rust=false" >> "$GITHUB_OUTPUT"
else
echo "rust=true" >> "$GITHUB_OUTPUT"
fi
fmt:
name: Rustfmt
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: rustfmt
- name: Check code formatting
run: cargo fmt --all --check
clippy:
name: Clippy
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy
- name: Run clippy
run: cargo clippy --all-targets -- -D warnings
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Lint shell scripts
run: shellcheck scripts/* tests/*.sh
- name: Check for machine-specific paths
run: bash scripts/check-machine-paths
- name: Test machine-path check
run: bash tests/test-check-machine-paths.sh
- name: Test fetch-github-copilot-usage
run: bash tests/test-fetch-github-copilot-usage.sh
- name: Test release script
run: bash tests/test-release.sh
- name: Test docs-only change detection
run: bash tests/test-docs-only-change.sh
- name: Test mutants-diff script
run: bash tests/test-mutants-diff.sh
package:
name: Package
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Dry-run cargo publish
run: cargo publish --dry-run
test:
name: Test (${{ matrix.os }})
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Run tests
run: cargo test --verbose
- name: Build release binary
run: cargo build --release --verbose
mutants:
name: Mutants
runs-on: ubuntu-latest
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.rust == 'true'
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install cargo-mutants and cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: cargo-mutants@27.1.0,cargo-nextest
- name: Run cargo-mutants on the diff
env:
BASE_REF: ${{ github.base_ref }}
MUTANTS_JOBS: 2
run: scripts/mutants-diff "origin/$BASE_REF"
- name: Summarize surviving mutants
if: always()
run: |
if [ -s mutants.out/missed.txt ]; then
{
echo '### Surviving mutants'
echo '```'
cat mutants.out/missed.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload mutants.out
if: always()
uses: actions/upload-artifact@v7
with:
name: mutants.out
path: mutants.out
if-no-files-found: ignore
ci-ok:
name: CI ok
if: always()
needs: [changes, fmt, clippy, shellcheck, package, test]
runs-on: ubuntu-latest
steps:
- name: Check job results
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
for result in $RESULTS; do
case "$result" in
success|skipped) ;;
*) echo "A required job ended with: $result"; exit 1 ;;
esac
done