Skip to content

Commit 438ba9d

Browse files
CI: Let a dispatch run one OS only (#1818) (#1820)
* CI: let a dispatch run one OS's jobs only A fix for a Windows-only nightly failure is checked with a manual dispatch of CI, which re-ran every Linux and macOS nightly-tier job too (~97 Linux and ~41 macOS job-minutes per run), although the fix's PR run and the merge queue already cover Linux. Add an `os` dispatch input (all, linux, windows, macos; default all). Each job's `if:` gets a term that is always true off dispatch, so pull_request, merge_group, push and the nightly run exactly what they ran before. clippy and release-readiness still run on every dispatch because every OS leg waits on them. Fixes #1818 Assisted-by: Claude Code:claude-opus-5-5 * CI: keep one-OS dispatches off ci-ok A dispatch that ran only one OS still reported `ci-ok`, the required check, on the branch head, so a Windows-only run could make a PR whose Linux jobs failed look green. Such a run now reports as `ci-ok (<os> only)`; every other run still reports `ci-ok`. Dispatches for different OSes on one branch also shared a concurrency group, so a third dispatch replaced the pending second one. Each OS-filtered dispatch now gets its own group. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b73b080 commit 438ba9d

3 files changed

Lines changed: 141 additions & 30 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ on:
2929
type: choice
3030
default: auto
3131
options: [auto, force, skip]
32+
os:
33+
# Run only one OS's jobs (plus the clippy and release-readiness
34+
# preflights every leg waits on). A fix for a Windows-only nightly
35+
# failure dispatches `-f os=windows` instead of re-running ~95 Linux
36+
# and macOS jobs (~140 job-minutes) its PR run and the merge queue
37+
# already cover. hosted-e2e is a Linux job and skips too. Only a
38+
# dispatch sets inputs.os; on every other event it is empty, so the
39+
# `inputs.os != ...` terms below are always true there. Each
40+
# OS-filtered dispatch gets its own concurrency group, so
41+
# dispatching one per OS runs them all.
42+
description: 'Only run jobs for this OS (all = the full nightly matrix)'
43+
type: choice
44+
default: all
45+
options: [all, linux, windows, macos]
3246

3347
permissions:
3448
contents: read
@@ -45,7 +59,7 @@ permissions:
4559
# cancelled the real run, skipped every job, and left ci-ok green on a head
4660
# nothing had tested.
4761
concurrency:
48-
group: ci-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }}${{ github.event.pull_request.draft && '-draft' || '' }}
62+
group: ci-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }}${{ github.event.pull_request.draft && '-draft' || '' }}${{ inputs.os && inputs.os != 'all' && format('-{0}', inputs.os) || '' }}
4963
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
5064

5165
# Linux jobs run on Depot's runners (depot-ubuntu-*-4: 4 vCPU, like GitHub's
@@ -144,7 +158,7 @@ jobs:
144158
# The napi addon is only ever loaded by Node, so cargo's own tests never
145159
# exercise its JS loader or the engine/provider boundary.
146160
node-addon:
147-
if: (github.event.pull_request.draft != true) && needs.release-readiness.outputs.pr_unchanged != 'true'
161+
if: ((github.event.pull_request.draft != true) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'windows' && inputs.os != 'macos')
148162
needs: [release-readiness, clippy]
149163
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
150164
timeout-minutes: 30
@@ -182,7 +196,7 @@ jobs:
182196
# Check the standalone installer, release scripts, and native installer
183197
# test harnesses.
184198
lint-ecosystems:
185-
if: github.event.pull_request.draft != true
199+
if: (github.event.pull_request.draft != true) && (inputs.os != 'windows' && inputs.os != 'macos')
186200
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
187201
timeout-minutes: 10
188202
steps:
@@ -362,7 +376,7 @@ jobs:
362376
bash scripts/release-lint.sh --sync-only
363377
364378
test:
365-
if: ((github.event.pull_request.draft != true) && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && needs.release-readiness.outputs.pr_unchanged != 'true'
379+
if: (((github.event.pull_request.draft != true) && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'linux')
366380
needs: [release-readiness, clippy]
367381
# No ubuntu-latest leg: `coverage` runs this same `cargo test
368382
# --workspace` (debug, default features, plus Go and vexctl) on ubuntu,
@@ -381,6 +395,8 @@ jobs:
381395
exclude:
382396
# macOS legs run on main, the merge queue and nightly, not per PR push.
383397
- os: ${{ github.event_name == 'pull_request' && 'macos-latest' || '' }}
398+
# `workflow_dispatch -f os=windows|macos` runs that OS's legs only.
399+
- os: ${{ inputs.os == 'windows' && 'macos-latest' || inputs.os == 'macos' && 'windows-latest' || '' }}
384400
# One compile-only cache writer per OS is enough for an already
385401
# tested SHA.
386402
- shard: ${{ needs.release-readiness.outputs.reuse == 'true' && 2 || 0 }}
@@ -559,7 +575,7 @@ jobs:
559575
# Each PR and main push runs the complete release-mode suite. The merge
560576
# queue already skips this job because each constituent PR ran it, so
561577
# an unchanged merge-main PR push (pr_unchanged) still runs it too.
562-
if: (github.event.pull_request.draft != true && github.event_name != 'merge_group') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
578+
if: ((github.event.pull_request.draft != true && github.event_name != 'merge_group') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && (inputs.os != 'windows' && inputs.os != 'macos')
563579
needs: clippy
564580
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-16' }}
565581
strategy:
@@ -607,7 +623,7 @@ jobs:
607623
run: python3 scripts/ci-test-shard.py "$TEST_SHARD" "$TEST_SHARD_COUNT" --locked --profile ci-release
608624

609625
coverage:
610-
if: (github.event.pull_request.draft != true) && needs.release-readiness.outputs.pr_unchanged != 'true'
626+
if: ((github.event.pull_request.draft != true) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'windows' && inputs.os != 'macos')
611627
needs: release-readiness
612628
# Code coverage via cargo-llvm-cov (LLVM source-based instrumentation).
613629
# Reports as a markdown table in the job summary and uploads the raw
@@ -765,7 +781,7 @@ jobs:
765781
# Dockerfile.base compiles the full-LTO release binary inside Docker, with
766782
# no cache. Build it once per run and hand the image to every docker leg.
767783
docker-base:
768-
if: ((github.event.pull_request.draft != true && needs.release-readiness.outputs.reuse != 'true') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.head_ref == 'release/v5-prerelease')) && needs.release-readiness.outputs.pr_unchanged != 'true'
784+
if: (((github.event.pull_request.draft != true && needs.release-readiness.outputs.reuse != 'true') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.head_ref == 'release/v5-prerelease')) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'windows' && inputs.os != 'macos')
769785
needs: [release-readiness, clippy]
770786
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-22.04' || 'depot-ubuntu-22.04-4' }}
771787
timeout-minutes: 30
@@ -1018,7 +1034,7 @@ jobs:
10181034
retention-days: 30
10191035

10201036
dispatch-tests:
1021-
if: github.event.pull_request.draft != true
1037+
if: (github.event.pull_request.draft != true) && (inputs.os != 'windows' && inputs.os != 'macos')
10221038
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
10231039
timeout-minutes: 10
10241040
steps:
@@ -1055,7 +1071,7 @@ jobs:
10551071
# test binaries directly from the same checkout path, so `CARGO_BIN_EXE_*`
10561072
# and `CARGO_MANIFEST_DIR` resolve as they did under `cargo test`.
10571073
e2e-build:
1058-
if: (github.event.pull_request.draft != true) && needs.release-readiness.outputs.pr_unchanged != 'true'
1074+
if: ((github.event.pull_request.draft != true) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'windows' && inputs.os != 'macos')
10591075
needs: release-readiness
10601076
outputs:
10611077
reuse: ${{ needs.release-readiness.outputs.reuse }}
@@ -1162,7 +1178,7 @@ jobs:
11621178
# Builds the Windows test binaries for e2e-windows and
11631179
# cargo-vex-matrix-windows. The lean merge queue runs
11641180
# windows-compile-check instead.
1165-
if: ((github.event.pull_request.draft != true) && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && needs.release-readiness.outputs.pr_unchanged != 'true'
1181+
if: (((github.event.pull_request.draft != true) && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'linux' && inputs.os != 'macos')
11661182
needs: [release-readiness, clippy]
11671183
outputs:
11681184
reuse: ${{ needs.release-readiness.outputs.reuse }}
@@ -1176,7 +1192,7 @@ jobs:
11761192
steps: *e2e-build-steps
11771193

11781194
e2e-build-macos:
1179-
if: (github.event_name != 'pull_request') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
1195+
if: ((github.event_name != 'pull_request') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && (inputs.os != 'linux' && inputs.os != 'windows')
11801196
needs: [release-readiness, clippy]
11811197
outputs:
11821198
reuse: ${{ needs.release-readiness.outputs.reuse }}
@@ -2332,7 +2348,7 @@ jobs:
23322348
# Dropping that `needs` also dropped the draft skip it inherited, so
23332349
# gate on draft here directly (push/merge_group/schedule still run).
23342350
# Only wait for the clippy and release-readiness preflights.
2335-
if: (github.event.pull_request.draft != true && needs.release-readiness.outputs.reuse != 'true') && needs.release-readiness.outputs.pr_unchanged != 'true'
2351+
if: ((github.event.pull_request.draft != true && needs.release-readiness.outputs.reuse != 'true') && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'windows' && inputs.os != 'macos')
23362352
needs: [release-readiness, clippy]
23372353
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
23382354
timeout-minutes: 40
@@ -2378,7 +2394,7 @@ jobs:
23782394
# Dropping that `needs` also dropped the draft skip it inherited, so
23792395
# gate on draft here directly (push/merge_group/schedule still run).
23802396
# Only wait for the clippy and release-readiness preflights.
2381-
if: (github.event.pull_request.draft != true && needs.release-readiness.outputs.reuse != 'true') && needs.release-readiness.outputs.pr_unchanged != 'true'
2397+
if: ((github.event.pull_request.draft != true && needs.release-readiness.outputs.reuse != 'true') && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'macos')
23822398
needs: [release-readiness, clippy]
23832399
strategy:
23842400
fail-fast: false
@@ -2387,7 +2403,7 @@ jobs:
23872403
# `10c0/` writer, current, and 4.12.0 on macOS / Windows. The rest
23882404
# of the spread (4.6.0, 4.12.0 on ubuntu) is yarn-berry-full.
23892405
# LEAN SCOPE: the newest Linux release only, unless CI_SCOPE=full / nightly.
2390-
include: ${{ fromJSON((vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '[{"os":"ubuntu-latest","yarn":"4.0.2"},{"os":"ubuntu-latest","yarn":"4.1.0"},{"os":"ubuntu-latest","yarn":"4.18.0"},{"os":"windows-latest","yarn":"4.12.0"}]' || '[{"os":"ubuntu-latest","yarn":"4.18.0"}]') }}
2406+
include: ${{ fromJSON((vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && (inputs.os == 'windows' && '[{"os":"windows-latest","yarn":"4.12.0"}]' || inputs.os == 'linux' && '[{"os":"ubuntu-latest","yarn":"4.0.2"},{"os":"ubuntu-latest","yarn":"4.1.0"},{"os":"ubuntu-latest","yarn":"4.18.0"}]' || '[{"os":"ubuntu-latest","yarn":"4.0.2"},{"os":"ubuntu-latest","yarn":"4.1.0"},{"os":"ubuntu-latest","yarn":"4.18.0"},{"os":"windows-latest","yarn":"4.12.0"}]') || '[{"os":"ubuntu-latest","yarn":"4.18.0"}]') }}
23912407
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS != 'true' && (matrix.os == 'ubuntu-latest' && 'depot-ubuntu-24.04-4' || matrix.os == 'ubuntu-22.04' && 'depot-ubuntu-22.04-4') || matrix.os }}
23922408
timeout-minutes: 30
23932409
steps: &yarn-berry-steps
@@ -2421,7 +2437,7 @@ jobs:
24212437
# nightly, not on every PR push, so PRs stop queueing on the small
24222438
# macOS runner pool.
24232439
yarn-berry-e2e-macos:
2424-
if: (github.event_name != 'pull_request' && needs.release-readiness.outputs.reuse != 'true') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
2440+
if: ((github.event_name != 'pull_request' && needs.release-readiness.outputs.reuse != 'true') && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && (inputs.os != 'linux' && inputs.os != 'windows')
24252441
name: yarn-berry ${{ matrix.yarn }} (${{ matrix.os }})
24262442
needs: [release-readiness, clippy]
24272443
strategy:
@@ -2439,7 +2455,7 @@ jobs:
24392455
yarn-berry-full:
24402456
name: yarn-berry ${{ matrix.yarn }} (${{ matrix.os }})
24412457
# Same gate as e2e-full.
2442-
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && vars.CI_SCOPE == 'full') || github.head_ref == 'release/v5-prerelease'
2458+
if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && vars.CI_SCOPE == 'full') || github.head_ref == 'release/v5-prerelease') && (inputs.os != 'windows' && inputs.os != 'macos')
24432459
needs: clippy
24442460
strategy:
24452461
fail-fast: false
@@ -2619,7 +2635,7 @@ jobs:
26192635
# Dropping that `needs` also dropped the draft skip it inherited, so
26202636
# gate on draft here directly (push/merge_group/schedule still run).
26212637
# Only wait for the clippy and release-readiness preflights.
2622-
if: ((github.event.pull_request.draft != true) && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && needs.release-readiness.outputs.pr_unchanged != 'true'
2638+
if: (((github.event.pull_request.draft != true) && (vars.CI_SCOPE == 'full' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')) && needs.release-readiness.outputs.pr_unchanged != 'true') && (inputs.os != 'windows' && inputs.os != 'macos')
26232639
needs: [release-readiness, clippy]
26242640
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
26252641
timeout-minutes: 30
@@ -2691,7 +2707,7 @@ jobs:
26912707
# ----------------------------------------------------------------------
26922708
hosted-e2e:
26932709
name: hosted-e2e # may be a required check; do not rename
2694-
if: github.event.pull_request.draft != true
2710+
if: (github.event.pull_request.draft != true) && (inputs.os != 'windows' && inputs.os != 'macos')
26952711
needs: clippy
26962712
runs-on: ${{ vars.DISABLE_DEPOT_RUNNERS == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
26972713
permissions:
@@ -2908,7 +2924,11 @@ jobs:
29082924
# every job above, so adding a job here is how it becomes merge-blocking.
29092925
# Skipped jobs (the nightly `full` tier) pass; failed or cancelled ones fail.
29102926
ci-ok:
2911-
name: ci-ok # registered as a required check; do not rename
2927+
# Registered as a required check; do not rename. A one-OS dispatch
2928+
# (inputs.os, #1818) skips the other OSes, so its verdict gets another
2929+
# name: a green `ci-ok` on the branch head must mean every PR-tier job
2930+
# passed, and a skipped required check would count as passing too.
2931+
name: ${{ inputs.os && inputs.os != 'all' && format('ci-ok ({0} only)', inputs.os) || 'ci-ok' }}
29122932
if: always()
29132933
needs: [clippy, node-addon, lint-ecosystems, release-readiness, test, test-release, coverage, docker-base, coverage-docker, coverage-merge, dispatch-tests, e2e-build, windows-compile-check, e2e-build-windows, e2e-build-macos, e2e, e2e-windows, e2e-extended, e2e-full, e2e-docker, yarn-classic-matrix, yarn-berry-e2e, yarn-berry-full, cargo-vex-matrix, cargo-vex-matrix-windows, cargo-vex-matrix-full, cargo-old-toolchains, hosted-e2e, e2e-macos, yarn-berry-e2e-macos, cargo-vex-matrix-macos]
29142934
# GitHub-hosted, not Depot: the check is one 2 s python3 call that runs

‎scripts/tests/test_ci_e2e_tiers.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,15 @@ def test_full_jobs_run_nightly_only_and_share_steps(self):
7777
# Nightly, dispatch (release-qa) and v5 landings, like
7878
# e2e-docker: not on PRs, the merge queue or a lean push to
7979
# main, where its 41 legs ran on every landing.
80-
self.assertIn(" if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"
81-
" || (github.event_name == 'push' && vars.CI_SCOPE == 'full')"
82-
" || github.head_ref == 'release/v5-prerelease'\n", text)
80+
# yarn-berry-full waits only on clippy, so it carries the
81+
# Linux-only term for `workflow_dispatch -f os=...` itself
82+
# (#1818); the others skip with e2e-build.
83+
gate = (" if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"
84+
" || (github.event_name == 'push' && vars.CI_SCOPE == 'full')"
85+
" || github.head_ref == 'release/v5-prerelease'")
86+
if job == "yarn-berry-full":
87+
gate = gate.replace(" if: ", " if: (") + ") && (inputs.os != 'windows' && inputs.os != 'macos')"
88+
self.assertIn(gate + "\n", text)
8389
self.assertIn(f"steps: *{anchor}", text)
8490
self.assertIn(f"steps: &{anchor}", TEXT)
8591

0 commit comments

Comments
 (0)