Repository navigation
ci(windows): run Windows debug and release builds as parallel jobs - #810
jussielo-amd wants to merge 1 commit into
Conversation
The single windows-build-and-test job was the longest pole of every CI run (about 16 min on main, longer on fork PRs, which get no shared compiler cache), and it started about 3 min late because it waited for the Linux clippy and prek jobs. - windows-debug: clippy, dev build, workspace tests, local smoke, harness self-tests. - windows-release: release build, install lifecycle e2e, pinned-key check, PR bundle packaging. These share no outputs with the debug side. - windows-build-and-test stays as the required check, now an aggregator that needs both jobs and `changes`, and requires each to have finished exactly `success`. A skip is accepted only for a manual dispatch that did not ask for binaries. Whether the Windows jobs run is decided once, as the new `changes.windows` output; both jobs gate on it and the aggregator derives its expected skip from it, so the two cannot disagree. - Neither Windows job waits for clippy or prek any more. Those remain required checks of their own, so a lint failure still blocks the merge; the cost is Windows runner time on pushes that turn out lint-red. Every step of the old job is carried over unchanged into exactly one new job (sccache stats runs in both). Contract tests in xtask pin the split: the aggregator awaits every job in the Windows lane and its script is driven over each possible job result; the work jobs' gating, needs and runner are pinned exactly, with no job-level continue-on-error; the two jobs' env and setup steps cannot drift; and each gating step is pinned by name with its command, exact `if:` and no continue-on-error. Stale mentions of the old single job in comments and docs are updated. This is CI plumbing and changes no CLI behaviour, so there is no Gherkin scenario. The new jobs have not yet run on GitHub; the first pull request run is the only check of the live wiring. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The rewired merge-required Windows CI gate lacks a live GitHub run, so a maintainer must confirm debug/release execution, aggregator results, and manual-dispatch skip behavior.
No open issues 🎉
What changed in this PR
This PR aims to shorten CI runs by splitting Windows debug and release work into parallel jobs, while preserving windows-build-and-test as the required check.
Changes:
- Separates debug validation from release builds, lifecycle checks, and packaging; removes both jobs’ wait for Linux lint checks.
- Adds a shared Windows execution condition and an aggregator that checks dependency results.
- Adds workflow contract tests and updates documentation and comments to reflect the split.
| File | Description |
|---|---|
xtask/src/workflow_contract.rs |
Adds contracts for Windows job gating, aggregation, setup consistency, and retained validation steps. |
xtask/src/env_mutation_contract.rs |
Updates comments identifying the job that runs Windows workspace tests. |
tests/e2e-cucumber/features/comfyui.feature |
Updates comments identifying the Windows lifecycle job. |
docs/testing.md |
Clarifies which Windows job runs workspace tests and lifecycle scenarios. |
.github/workflows/e2e-selfhosted.yml |
Updates comments distinguishing hosted Windows jobs from GPU coverage. |
.github/workflows/ci.yml |
Splits Windows work into parallel jobs and adds the required-check aggregator. |
Review effort: Balanced
juhovainio
left a comment
There was a problem hiding this comment.
I reviewed this PR and found nothing blocking. Two small notes are left inline.
What I checked:
- The aggregator job
windows-build-and-testkeeps its name, so the required-check match still holds. It needschanges,windows-debugandwindows-release, uses!cancelled(), and requires an exact result from each. - Every old step now lives in exactly one of the new jobs, and the release job keeps its
AWS_LC_SYS_USE_SYSTEMenv. - Action pins are unchanged, no stale references to the old single job remain in docs, features or xtask, and I saw no leaks.
Notes:
- On this PR's cold-cache run
windows-releasetook 20m31s against 14m46s forwindows-debug, so the lanes are unbalanced and the gain is still unmeasured. - The two new work jobs are not required checks themselves, so the aggregator needs to stay in the branch-protection ruleset.
- Dropping the wait on clippy/prek is a documented, deliberate tradeoff.
Not verified: the "41 mutation checks" claim, and I did not run the xtask tests. Two Strix Halo Ubuntu lanes (nightly, release) failed at 45m25s on files this PR does not touch, likely runner infra, but I did not confirm the cause. This reflects a diff read, not a full CI result.
| # Release side of the Windows lane. It builds the optimised binaries, and the | ||
| # install lifecycle and the PR bundle consume them. The release-readiness | ||
| # self-test, the installer syntax check and the pinned-key check need no binaries; | ||
| # they sit here only to keep the two jobs about the same length. See |
There was a problem hiding this comment.
On this PR's run windows-release took 20m31s vs windows-debug 14m46s, so the two are not about the same length yet (cold caches on a PR). Worth re-measuring on a main push and rebalancing if the release side stays the long pole.
|
|
||
| The required `windows-build-and-test` lane additionally folds in the hermetic | ||
| The `windows-release` job of the required `windows-build-and-test` lane additionally | ||
| folds in the hermetic |
There was a problem hiding this comment.
Nit: the rewrap leaves "folds in the hermetic" as an orphan line; reflow the paragraph.
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · ce854ce
This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.
Review — no blocking findings
Full review of the whole change (base df1548a2…head ce854ce9). Purely CI plumbing: splits the Windows GitHub Actions lane into two parallel jobs behind a pinned aggregator; no ticket named in the description, and none appears needed for a CI-only change.
Blocking
None
Non-blocking
ci_jobs()'s comment-attachment differs from the pre-existingjob_block()'s, which is a latent fragility in the new contract-test helpers —xtask/src/workflow_contract.rs:4252-4277(newci_jobs/ci_job) vsxtask/src/workflow_contract.rs:303-320(pre-existingjob_block).
job_blockstops a job's extracted text at the first indent-2 line, comment or not, so a trailing comment before the next job is excluded from both jobs.ci_jobsonly starts a new job at an indent-2, non-comment line, so a trailing comment block that precedes a job header (e.g. the rationale comment right beforewindows-build-and-test:, which itself contains the literal strings!cancelled()andalways()) is folded into the previous job's text instead of being excluded or attached to the job it actually describes.ci_windows_aggregator_requires_every_windows_jobthen doescode.contains("!cancelled()") && !code.contains("always()")overci_job(&ci, "windows-build-and-test"); today this still evaluates correctly only because the job's ownif:line repeats!cancelled()and the misattributed comment'salways()mention lands inwindows-release's captured block instead, outside what the assertion checks. A later edit to that comment, or a new trailing comment before any job this helper is also used on, could make the assertion pass or fail for the wrong reason without anyone noticing, which runs against the rest of this test file's evident care about not trusting a check for the wrong reason.
Confidence 70 · architectural · Fix: makeci_jobs's job-boundary rule matchjob_block's (stop at any indent-2 line), or have it drop a job's leading comment lines instead of attaching them to whichever job happened to be parsed immediately before.
Decisions for the author
- Dropping
windows-debug/windows-release'sneeds: [changes, clippy, prek]relies onclippyandprekalready being independently-required branch-protection checks — tradeoff.ci.yml's own comment states "Those stay required checks in their own right, so a lint failure still blocks the merge," which is a claim about branch-protection configuration that isn't visible from the files in this repo. It is very likely already true independent of this change — dozens of other jobs in the same file (e.g.test,build-and-test) stillneed: [changes, clippy, prek]purely as a compute-saving gate, a pattern that only makes sense ifclippy/prekare already required on their own — and the pre-split job'sif:treated aclippy/prekskip the same as success, so this PR does not newly introduce reliance on "skipped reads as passing" for the Windows lane; if anything it now produces a real success/failure forwindows-debug/windows-releaseinstead of skipping them outright when lint is red. Worth a quick confirmation against the actual ruleset before merge, since nothing in-repo proves it. - Running two parallel
windows-latestjobs instead of one serial job duplicates per-job fixed overhead (checkout, sccache install, the cache-backend probe, toolchain setup) that was previously paid once — tradeoff. The change's own comments already weigh the clippy/prek-wait removal's cost in wall-clock terms, but not this one in Windows-runner-minutes: total billed Windows compute time may rise somewhat even as end-to-end latency falls. Worth confirming that's an accepted cost, alongside the already-documented cold-cache effect on both jobs' first runs.
Positive signals
ci_windows_aggregator_passes_only_when_every_job_succeededdirectly executes the aggregator's extracted bash script underbash -e -cagainst every result/skip combination (success/failure/cancelled/skipped × expected-skip true/false) rather than only asserting on source text — real behavioral verification of the merge gate, independently re-run and confirmed correct for this review.- Every step from the pre-split monolithic job lands in exactly one of
windows-debug/windows-release, confirmed by diffing the old single job against the new pair step-by-step; nothing was silently dropped or duplicated. - The four supporting files (
docs/testing.md,tests/e2e-cucumber/features/comfyui.feature,xtask/src/env_mutation_contract.rs,.github/workflows/e2e-selfhosted.yml) were each updated to name the correct new job for the behavior they describe — including the non-obvious case that the install-lifecycle/E2E_ALSO_RUN_IDSwork moved towindows-releasewhile the single-processcargo test --workspacemoved towindows-debug, which would have been an easy pair to swap by mistake. ci_windows_jobs_share_one_cache_and_toolchain_setupguards the two duplicated jobs' env and setup steps against silent drift from each other, andci_windows_jobs_keep_every_gating_step_enabledpins every gating step's exact command,if:, and absence ofcontinue-on-error— both checked by direct reading against the literal YAML, including the trickier>-block-scalar steps.- All
uses:lines are pinned to full commit SHAs with trailing version comments, consistent with this repo's own convention; single, clearly-written, DCO-signed commit whose message matches the diff.
Deployment notes
- At head commit
ce854ce9, two self-hosted GPU jobs in the separate "E2E self-hosted" workflow — "E2E tests (Strix Halo, Ubuntu, nightly, current)" and "...release, current)" — show cancelled, and two more ("E2E tests (MI300X, current)", "E2E tests (W7900, current)") remain queued. This PR touches that workflow with only a verified-accurate comment edit, the job logs show an artifact upload immediately followed by orphan-process termination (consistent with a runner-side cancellation rather than a failing assertion), and this repo's ownAGENTS.mddocuments that self-hosted GPU hardware availability can differ from a clean run. All checks this change can actually affect — thewindows-build-and-testaggregator,windows-debug,windows-release,clippy,prek, and the affected-crates test job that compiles/runs the new contract tests — are green. Recommend confirming the self-hosted lanes are unrelated infrastructure flakiness before treating the run as fully clean.
What this covered
Every one of the 6 changed files read completely at the PR's head content (fetched via git show refs/pr810head:<path>, never checked out), plus unchanged repository context (module wiring in xtask/src/main.rs, release.yml/nightly.yml, README.md, other docs) for impact analysis — via two independent blind fan-out workers (one on ci.yml + xtask/src/workflow_contract.rs, one on the four smaller doc/comment files), each scoped to files only, with no access to this PR's description or comments, plus my own consolidation, impact-analysis and design-review pass. Range reviewed: df1548a211ed5a53d37d2e5df8f83a7746a13239…ce854ce901021150f06ba765d8286e0e197a5e9e.
Procedural note: as the orchestrating context I necessarily read this PR's title/body once during the mandatory pre-review duplicate-review check (the API call that returns the head SHA also returns the body); neither fan-out worker saw it, and its content was not used for judgment until after the findings above were settled.
Live CI was read at the exact head commit (gh pr checks): all Windows- and lint-relevant jobs are SUCCESS, as detailed under Deployment notes. "Prior changes" — no earlier review threads exist on this PR to reconcile against (confirmed zero review-comment threads). "History" — compared the pre-split job definition on origin/main against the new pair directly via git show/git diff. Could not execute cargo test -p xtask directly (no Rust toolchain available in this environment); relied on the live CI's own passing "Test (affected crates)" job plus direct re-execution of the aggregator's bash script and manual tracing of the YAML-parsing helpers against the literal file content. Not reconciled against any discussion on this PR.
Summary
The Windows build-and-test job is the longest pole of every CI run: about 16 minutes on main and 20+ on pull requests from forks, which get no shared compiler cache. It also starts about 3 minutes late because it waits for the Linux
clippyandprekjobs. This splits it into two parallel jobs and removes that wait. By step timings of recent runs, main should drop from about 16 minutes to about 10 (an estimate, not yet measured).What changed
windows-debug: Windows clippy, dev build, workspace tests, local smoke, and the harness self-tests.windows-release: release build, install lifecycle e2e, release-readiness self-test, installer syntax check, pinned-key check, and PR bundle packaging. None of these consume the debug side's output.windows-build-and-testkeeps its name as the required check and becomes an aggregator that needschanges,windows-debugandwindows-release, and requires each to have finished exactlysuccess. A skip is accepted only for a manual dispatch that did not ask for binaries.changes.windowsoutput. Both jobs gate on it and the aggregator derives its expected skip from it, so the two cannot disagree.clippyorprekany more. Both remain required checks of their own, so a lint failure still blocks the merge.sccache statsruns in both). I checked this by diffing the parsed steps before and after.Why the gate is not weakened
A skipped required check reads as passing, so the aggregator compares each result for exact equality instead of testing for failure:
failure,cancelledand an unexpectedskippedall fail it, and a failedchangesfails it even when the work jobs are meant to be skipped. The new contract tests inxtask/src/workflow_contract.rspin this: the aggregator must await every job in the Windows lane (found by runner, with an explicit allowlist forpowershell-lint), its script is executed over every combination of job results, the work jobs'needs, runner andif:are pinned exactly with no job-levelcontinue-on-error, the two jobs' env and setup steps cannot drift apart, and each gating step is pinned by name with its command and exactif:. I applied 41 deliberate breakages toci.yml(dropped steps,continue-on-error,if: false, wrongneeds, fork-skipping conditions, a Windows job the aggregator does not await, and so on) and each one fails a Windows contract test.Trade-offs
clippy/prekwait buys about 2.5 minutes of wall clock on every run, paid for with Windows runner time on pushes that turn out lint-red. A middle option isneeds: [changes, prek], which costs about 20 seconds; happy to switch if preferred.windows-build-and-testcache entry is orphaned until it ages out. The sccache prefix is unchanged, so the compiler cache stays warm.cargo xtask e2ebuilds the e2e harness in the dev profile, which now happens inwindows-releaseinstead of being warmed by the debug build. sccache should absorb this after the first run.Verification
cargo test -p xtask(346 tests),cargo fmt --checkandcargo clippy -p xtask --all-targets -- -D warningspass locally, and the leak scan and conflict-marker grep are clean.ci.yml, a few comments and docs, and xtask test code, and CI runs the workspace.actionlintwas not available locally.This is CI plumbing and changes no CLI behaviour, so there is no Gherkin scenario.