name: CI
on:
push:
branches: [ main, nightly ]
pull_request:
branches: [ main, nightly ]
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 with:
key: lint
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@86f7a0ffb25f40114606501a311ebcfaf882ea32 with:
tool: cargo-machete,cargo-deny
- name: Formatting
run: cargo fmt -- --check
- name: Clippy (default features)
run: cargo clippy --all-targets -- -D warnings
- name: Clippy (no default features)
run: cargo clippy --all-targets --no-default-features -- -D warnings
- name: Unused dependencies
run: cargo machete
- name: Dependency policy
run: cargo deny --all-features check
- name: Workflow security
uses: zizmorcore/zizmor-action@53290badfa369cb0bf7ad16342cb62c2fa47ac7b with:
advanced-security: false
annotations: true
min-severity: low
policy:
name: Policy
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
fetch-depth: 0
- uses: taiki-e/install-action@86f7a0ffb25f40114606501a311ebcfaf882ea32 with:
tool: similarity-rs
- name: A change to what ships bumps the version
env:
BASE_REF: ${{ github.base_ref }}
AUTHOR: ${{ github.event.pull_request.user.login }}
run: |
shipped=$(git diff --name-only "origin/${BASE_REF}..." -- src assets Cargo.toml Cargo.lock)
if [ -z "$shipped" ] || [ "$AUTHOR" = "dependabot[bot]" ]; then
echo "Nothing that ships changed, or this is Dependabot; no bump needed."
exit 0
fi
version() { sed -n 's/^version = "\(.*\)"$/\1/p' | head -n 1; }
base=$(git show "origin/${BASE_REF}:Cargo.toml" | version)
head=$(version < Cargo.toml)
newest=$(printf '%s\n%s\n' "$base" "$head" | sort -V | tail -n 1)
if [ "$head" = "$base" ] || [ "$newest" != "$head" ]; then
echo "::error file=Cargo.toml::This PR changes what ships, so bump the version above $base (it is $head)."
exit 1
fi
echo "Version $base -> $head" | tee -a "$GITHUB_STEP_SUMMARY"
- name: No new similar functions
env:
BASE_REF: ${{ github.base_ref }}
run: |
status=0
scripts/similar-code.sh "origin/${BASE_REF}" > report.txt || status=$?
cat report.txt
{
echo "### Similar code"
echo '```'
sed -E 's/^::(warning|error) file=([^,]+),line=([0-9]+),title=Similar code::/\2:\3: \1: /' report.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
exit "$status"
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 with:
shared-key: test
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Tests (default features)
run: cargo test
- name: Tests (no default features)
run: cargo test --no-default-features
- name: Contract
run: |
cargo install cargo-impish --version 0.6.1 --locked
cargo impish check
- name: Contract coverage summary
run: |
jq -r '
"## Contract coverage",
"",
"\(.obligations) obligations; \(.surface.covered) of \(.surface.total) commands and tools covered (\(.surface.percent)%).",
"",
"| Primitive | Obligations | Surface |",
"| --- | --- | --- |",
(.primitives | to_entries[] | "| \(.key) | \(.value.obligations) | \(.value.surface | join(", ")) |"),
"",
"<details><summary>Not covered (\(.uncovered | length))</summary>",
"",
(.uncovered | join(", ")),
"",
"</details>"
' contract/coverage.json >> "$GITHUB_STEP_SUMMARY"
portability:
name: Portability (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Tests
run: cargo test
mutants:
name: Mutants
needs: [lint, policy, test]
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
fetch-depth: 0
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 with:
shared-key: test
save-if: false
- uses: taiki-e/install-action@86f7a0ffb25f40114606501a311ebcfaf882ea32 with:
tool: cargo-mutants
- name: Diff against base
env:
BASE_REF: ${{ github.base_ref }}
run: git diff "origin/${BASE_REF}.." | tee git.diff
- name: Mutants
run: cargo mutants --no-shuffle -vV --in-place --in-diff git.diff
- name: Archive mutants.out
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 if: always()
with:
name: mutants.out
path: mutants.out
ci:
name: CI
needs: [lint, policy, test, portability, mutants]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Every job passed or was skipped
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
echo "$RESULTS"
case " $RESULTS " in *" failure "*|*" cancelled "*) exit 1 ;; esac
new-version:
name: New version?
needs: ci
if: >-
!cancelled() && needs.ci.result == 'success'
&& github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
tag: ${{ steps.version.outputs.tag }}
new: ${{ steps.version.outputs.new }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- id: version
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="v$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)"
if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${tag}" --silent 2>/dev/null; then
new=false
else
new=true
fi
echo "${tag}: new=${new}" | tee -a "$GITHUB_STEP_SUMMARY"
printf 'tag=%s\nnew=%s\n' "$tag" "$new" >> "$GITHUB_OUTPUT"
release:
name: Release
needs: new-version
if: >-
!cancelled() && needs.new-version.result == 'success'
&& needs.new-version.outputs.new == 'true'
permissions:
contents: write
pages: write
id-token: write
uses: $/.github/workflows/release.yml
with:
tag: ${{ needs.new-version.outputs.tag }}
secrets:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}