From 713ced76043beaaf7f0166dcca7e8fd12a0e0973 Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sun, 1 Nov 2026 18:01:10 +0800 Subject: [PATCH 1/5] perf(ci): use a prebuilt actionlint and run Rust tests with nextest The actionlint Docker action rebuilt its image on every run (about 30 s). The release binary is checksum-pinned and takes about 2 s. rust-quality runs tests with cargo-nextest, which overlaps test binaries that cargo test runs one after another, plus a separate doctest pass. A new skip input lets callers skip the job for documentation-only changes while the required check name still reports. --- .github/workflows/actionlint.yml | 27 +++++++++++++++++++++++++-- .github/workflows/rust-quality.yml | 24 ++++++++++++++++++++---- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 6b4aed2..5a089b3 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -11,6 +11,10 @@ on: paths: - ".github/workflows/**" +concurrency: + group: actionlint-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: actionlint: name: Lint workflows @@ -18,9 +22,28 @@ jobs: timeout-minutes: 5 permissions: contents: read + env: + # Bump both values together; the checksum is in the release's + # actionlint__checksums.txt. + ACTIONLINT_VERSION: 1.7.12 + ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 steps: - - name: Check out repository + - name: Check out workflows uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + sparse-checkout: .github + + # The prebuilt release binary replaces the rhysd/actionlint Docker + # action, which rebuilt its image on every run. The runner image + # already provides shellcheck, which actionlint uses for `run:` scripts. + - name: Install actionlint + run: | + set -euo pipefail + archive="$RUNNER_TEMP/actionlint.tar.gz" + curl -fsSL --retry 3 -o "$archive" \ + "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" + echo "${ACTIONLINT_SHA256} ${archive}" | sha256sum --check --strict + tar -xzf "$archive" -C "$RUNNER_TEMP" actionlint - name: Run actionlint - uses: rhysd/actionlint@914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12 + run: '"$RUNNER_TEMP/actionlint" -color' diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index eac8845..31ca783 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -68,10 +68,16 @@ on: required: false type: boolean default: true + skip: + description: Skip the job, for a change that touches no code; a skipped job still reports its check name as passing + required: false + type: boolean + default: false jobs: rust-quality: name: Quality + if: ${{ !inputs.skip }} runs-on: ${{ inputs.runs-on }} timeout-minutes: ${{ inputs.timeout-minutes }} permissions: @@ -151,6 +157,13 @@ jobs: fi cargo clippy --locked "${cargo_args[@]}" -- -D warnings + # Prebuilt binary: nextest overlaps the test binaries that `cargo test` + # runs one after another. + - name: Install cargo-nextest + uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1 + with: + tool: nextest + - name: Run tests shell: bash env: @@ -165,9 +178,6 @@ jobs: if [[ "${WORKSPACE_FLAG}" == '--workspace' ]]; then cargo_args+=(--workspace) fi - if [[ "${TARGETS_FLAG}" == '--all-targets' ]]; then - cargo_args+=(--all-targets) - fi if [[ "${NO_DEFAULT_FLAG}" == '--no-default-features' ]]; then cargo_args+=(--no-default-features) fi @@ -177,4 +187,10 @@ jobs: if [[ -n "${EXPLICIT_FEATURES}" ]]; then cargo_args+=("--features=${EXPLICIT_FEATURES}") fi - cargo test --locked "${cargo_args[@]}" + if [[ "${TARGETS_FLAG}" == '--all-targets' ]]; then + cargo nextest run --locked "${cargo_args[@]}" --all-targets + else + cargo nextest run --locked "${cargo_args[@]}" + fi + # nextest does not run doctests. + cargo test --locked --doc "${cargo_args[@]}" From 7dc272025b24733ae42f951d89642a79711aad1e Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sun, 1 Nov 2026 18:14:51 +0800 Subject: [PATCH 2/5] perf(ci): keep cargo test; nextest was slower on hosted runners Measured on pull request runs: opy-rs Run tests went from 52 s to 149 s, athena from 40 s to 65 s. Per-test processes cost more than overlapping the test binaries saves. The skip input stays. --- .github/workflows/rust-quality.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index 31ca783..ee7f2be 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -157,13 +157,6 @@ jobs: fi cargo clippy --locked "${cargo_args[@]}" -- -D warnings - # Prebuilt binary: nextest overlaps the test binaries that `cargo test` - # runs one after another. - - name: Install cargo-nextest - uses: taiki-e/install-action@68a389c788b2cd04016ce8c80ee23b80d2ab3d68 # v2.88.1 - with: - tool: nextest - - name: Run tests shell: bash env: @@ -178,6 +171,9 @@ jobs: if [[ "${WORKSPACE_FLAG}" == '--workspace' ]]; then cargo_args+=(--workspace) fi + if [[ "${TARGETS_FLAG}" == '--all-targets' ]]; then + cargo_args+=(--all-targets) + fi if [[ "${NO_DEFAULT_FLAG}" == '--no-default-features' ]]; then cargo_args+=(--no-default-features) fi @@ -187,10 +183,4 @@ jobs: if [[ -n "${EXPLICIT_FEATURES}" ]]; then cargo_args+=("--features=${EXPLICIT_FEATURES}") fi - if [[ "${TARGETS_FLAG}" == '--all-targets' ]]; then - cargo nextest run --locked "${cargo_args[@]}" --all-targets - else - cargo nextest run --locked "${cargo_args[@]}" - fi - # nextest does not run doctests. - cargo test --locked --doc "${cargo_args[@]}" + cargo test --locked "${cargo_args[@]}" From 957ff5a54b219879389deea339c841a41fabc888 Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sun, 1 Nov 2026 18:25:57 +0800 Subject: [PATCH 3/5] ci: TEMP seed the quality cache from the PR --- .github/workflows/rust-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index ee7f2be..1348612 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -97,7 +97,7 @@ jobs: targets: ${{ inputs.targets }} cache-shared-key: ${{ inputs.cache-shared-key }} cache-targets: ${{ inputs.cache-targets }} - cache-save: ${{ github.ref == 'refs/heads/main' }} + cache-save: true # TEMP: seed in the PR to measure workspaces: ${{ inputs.working-directory }} -> target - name: Check formatting From 8a95de76c77c0d338664bdea348d449fec27195f Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sun, 1 Nov 2026 18:40:37 +0800 Subject: [PATCH 4/5] docs(ci): record the run-speed rules Cancel superseded pull request runs only, filter documentation-only changes inside the workflow so required checks still report, avoid needs without data, count jobs, prefer prebuilt tools, and keep cargo test. --- docs/ci-platform.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/ci-platform.md b/docs/ci-platform.md index af9ae0d..16653f5 100644 --- a/docs/ci-platform.md +++ b/docs/ci-platform.md @@ -59,3 +59,43 @@ release. Rust quality cache policy follows `docs/rust-ci.md`: compatible jobs restore one stable family, only successful `main` quality runs save it, failed jobs do not save it, and release/tag workflows do not use the quality cache. + +## Run speed + +These rules keep a CI run short without removing a check. + +- **Cancel superseded pull request runs only.** Give the CI workflow + `concurrency.group: ci-${{ github.event.pull_request.number || github.sha }}` + and `cancel-in-progress: ${{ github.event_name == 'pull_request' }}`. A push + to `main` gets a unique group, so every `main` commit keeps a complete run; + the release gate depends on that. A workflow that also reacts to title or + label events adds a separate group suffix for them, so they cannot cancel a + running suite. +- **Filter documentation-only changes inside the workflow, not with `on.paths`.** + A workflow skipped by `paths` or `paths-ignore` never reports its checks, and + a required check that never reports blocks the merge. A `changes` job built on + `dorny/paths-filter` with `predicate-quantifier: every` computes `code`; + callers pass `skip: ${{ needs.changes.outputs.code != 'true' }}` to + `rust-quality.yml` or put `if: needs.changes.outputs.code == 'true'` on their + own jobs. A skipped job reports its check name as skipped, which counts as + passing for a required check. The release gate accepts only `success`, so it + must never depend on a check that a release commit can skip: a release commit + changes `Cargo.toml`, which is code. A repository with no required check may + filter with `paths-ignore` instead. +- **Do not chain jobs that exchange no data.** Drop a `needs` that only orders + jobs. Keep `needs` for artifacts and outputs, and for the aggregate job. +- **Count jobs.** A free organization runs 20 jobs at once, and 5 of them on + macOS. A run that starts more jobs than that queues the rest, so merge many + small jobs into one job with a loop, or into a few shards. +- **Put checks that do not depend on the operating system on Ubuntu.** Use macOS + and Windows only for installers, packaging, and platform runtime behavior. +- **Prefer prebuilt tools.** Download a checksum-pinned release binary, or use an + install action that downloads one. Do not run `cargo install` or `go install` + in a job. `actionlint.yml` installs the release binary and verifies its + SHA-256. +- **Keep `cargo test`.** `cargo nextest` was measured on the hosted runners and + was slower for opy-rs (52 s to 149 s) and athena (40 s to 65 s), because it + starts one process per test. +- **Optimize a dependency that dominates a debug test.** A + `[profile.dev.package.]` `opt-level` entry costs one cached compile and + can remove minutes of test time; Wright sets one for `workshop-rs`. From e947b8ee6b7d918b945316f413629380ac6e12b4 Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sun, 1 Nov 2026 18:40:38 +0800 Subject: [PATCH 5/5] ci: save the quality cache from main only again --- .github/workflows/rust-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-quality.yml b/.github/workflows/rust-quality.yml index 1348612..ee7f2be 100644 --- a/.github/workflows/rust-quality.yml +++ b/.github/workflows/rust-quality.yml @@ -97,7 +97,7 @@ jobs: targets: ${{ inputs.targets }} cache-shared-key: ${{ inputs.cache-shared-key }} cache-targets: ${{ inputs.cache-targets }} - cache-save: true # TEMP: seed in the PR to measure + cache-save: ${{ github.ref == 'refs/heads/main' }} workspaces: ${{ inputs.working-directory }} -> target - name: Check formatting