From e562d007d8587dde50d5345e6003f39b77e38e39 Mon Sep 17 00:00:00 2001 From: Tejas Chopra Date: Mon, 27 Jul 2026 07:34:00 -0700 Subject: [PATCH] ci(rust): gate jobs with `if:` instead of a workflow-level paths filter (#2580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prerequisite for making `parity` a required status check on main. A workflow skipped by a top-level `paths:` filter never creates its check runs at all, so a required check sourced from it sits pending forever on any PR that misses those paths and the PR can never merge. A job skipped by `if:` still creates a check run, reports skipped, and GitHub counts skipped as success. Moves the seven path patterns verbatim from the `on:` block into a new `rust-changes` job (dorny/paths-filter) and gates all five existing jobs on `needs.rust-changes.outputs.rust == 'true'`. Named rust-changes, not changes, to avoid colliding with ci.yml's existing check. Job `name:` fields unchanged, so no check names move. schedule/workflow_dispatch force rust=true, preserving the nightly full-suite behaviour. CI spend unchanged: same jobs on the same PRs, plus a ~15s gate job on PRs that previously skipped the workflow outright. Verified by the PR itself — it edits rust.yml, which is in the path list, so it exercises the rust=true branch: rust-changes and parity both SUCCESS. Follow-up before adding parity to required checks: confirm a non-Rust PR reports parity as skipped rather than absent. ci.yml has the mirror-image problem (paths-ignore on docs) and is deliberately not addressed here. --- .github/workflows/rust.yml | 68 +++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b50c0bbda..b192b5ac8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,25 +1,18 @@ name: rust +# Path gating lives in the `rust-changes` job below, NOT in a workflow-level +# `paths:` filter. The distinction matters for branch protection: a workflow +# skipped by `paths:` never creates its check runs at all, so a required status +# check from it sits pending forever on any PR that misses those paths, and the +# PR can never merge. A job skipped by `if:` still creates a check run, reports +# `skipped`, and GitHub counts skipped as success for a required check. +# +# Same coverage as the old filter — the path list moved verbatim into +# `rust-changes` — but `parity` is now safe to mark required on `main`. on: push: branches: [ main, rust-rewrite ] - paths: - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - 'rust-toolchain.toml' - - 'tests/parity/**' - - 'Makefile' - - '.github/workflows/rust.yml' pull_request: - paths: - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - 'rust-toolchain.toml' - - 'tests/parity/**' - - 'Makefile' - - '.github/workflows/rust.yml' schedule: # Nightly parity run at 07:17 UTC (weekdays only). Redundant with the # per-PR gate below, but catches drift from toolchain/dependency updates @@ -38,8 +31,43 @@ permissions: contents: read jobs: + # Carries the path list the workflow-level `paths:` filter used to hold. Named + # `rust-changes` rather than `changes` so it does not collide with ci.yml's + # `changes` check. + rust-changes: + name: rust-changes + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + rust: ${{ steps.decide.outputs.rust }} + steps: + - uses: actions/checkout@v7 + - uses: dorny/paths-filter@v4 + id: filter + if: github.event_name == 'pull_request' || github.event_name == 'push' + with: + filters: | + rust: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'rust-toolchain.toml' + - 'tests/parity/**' + - 'Makefile' + - '.github/workflows/rust.yml' + - id: decide + # `schedule` and `workflow_dispatch` have no diff to filter against, so + # they run the full suite — that is the point of the nightly job. + run: | + case "${{ github.event_name }}" in + pull_request|push) echo "rust=${{ steps.filter.outputs.rust }}" >> "$GITHUB_OUTPUT" ;; + *) echo "rust=true" >> "$GITHUB_OUTPUT" ;; + esac + test: name: test (ubuntu) + needs: rust-changes + if: needs.rust-changes.outputs.rust == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -78,6 +106,8 @@ jobs: simulator-e2e: name: simulator e2e (${{ matrix.os }}) + needs: rust-changes + if: needs.rust-changes.outputs.rust == 'true' runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: @@ -97,6 +127,8 @@ jobs: wheels: name: wheels (${{ matrix.target }}) + needs: rust-changes + if: needs.rust-changes.outputs.rust == 'true' runs-on: ${{ matrix.os }} timeout-minutes: 45 strategy: @@ -137,6 +169,8 @@ jobs: audit: name: audit + needs: rust-changes + if: needs.rust-changes.outputs.rust == 'true' runs-on: ubuntu-latest timeout-minutes: 20 steps: @@ -168,6 +202,8 @@ jobs: # drifting away from the fixtures; that needs re-recording, not this job. parity: name: parity + needs: rust-changes + if: needs.rust-changes.outputs.rust == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v7