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..ee7f2be 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: 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`.