ci(cmux-next): DEV Release compile builds whole-module when it has no host cache

Merged
TE
8d ago
+97-14

The DEV Release compile picks its Swift compilation mode from whether the build has a host compilation cache. A build that has none now compiles whole-module, not singlefile. This is the cold-runner follow-up to #24994.

What changed

  • scripts/cmux-next/compile-dev-release.sh resolves CMUX_NEXT_SWIFT_COMPILATION_MODE=auto from the build mode it has already chosen:
    • host mode (the per-host tree and DerivedData, under the host lock): singlefile. That is what the host cache holds and replays. This includes a host's first build (no previous_sha), which compiles singlefile so it seeds the cache. A whole-module first build would leave no singlefile entries, so the next landing on that host would be cold again. Placement also records any host build as a warm cache holder (dev-cas-holder.sh), so a whole-module seed would send the next landing to a host whose cache it can't use.
    • instance mode (another build holds the host root, host tree setup failed, cache off, or not a mini, which includes the Blacksmith fallback): wholemodule. These builds have no cache to replay and never fill the host's cache.
    • An explicit wholemodule or singlefile wins; anything else exits 2.
  • It logs the choice and the reason, e.g. compile-dev-release: cache cold: another build holds the host root -> wholemodule, host cache from <sha> -> singlefile, or new host tree: singlefile seeds the host cache -> singlefile. The report file and $GITHUB_OUTPUT get swift_mode=, and the per-landing notice line now prints swift_mode=.
  • .github/workflows/cmux-next.yml: every vars.CMUX_NEXT_DEV_SWIFT_COMPILATION_MODE || 'singlefile' becomes || 'auto'. That covers both compile steps, both product-reuse lookups and both REF-settings blocks.

Product reuse and the REF capture

The reuse key's swift_mode= extra now names the policy (auto), not the mode a build resolved to. Under auto, the product of a set of inputs is either a singlefile or a whole-module Release -O build of the same sources. Either is a valid DEV app, and every DEV app was whole-module before #24994. Keying by the resolved mode isn't possible anyway, because the lookup runs before the build knows its mode. A repo variable that names a mode still changes the key, as before. The script is part of the fingerprinted tree, so this PR changes the key by itself and existing records aren't reused across it.

HYBRID_REF_SETTINGS also says auto: test_the_capture_uses_the_compile_settings requires it to match the compile step's env. The REF capture only runs when build_mode == host, which resolves to singlefile, and its manifest records the real xcodebuild arguments.

Performance

xcodebuild wall time comes from the job logs' xcodebuild diagnostic wrapper start/finish lines, and cache state from CompilationCacheMetrics (note: N hits / 509 cacheable tasks). The data is 15 DEV landings on 28 Oct, all on Xcode 26.6.

metricbeforeafterdeltarun link
instance-mode build (0/509 hits; here the host root was busy)804 s singlefile~545 s whole-module (expected)about -260 s (-32%)40723039421
whole-module, no cache (the #24994 baseline)518 / 545 / 560 s518 / 545 / 560 s (unchanged)0 (baseline)#24994
host build, warm (509/509 hits)111 / 124 / 136 / 248 s singlefileunchanged (singlefile)040732582859, 40728187140, 40734056576, 40719692915
host build, first in a new host tree (0/509 hits)750 / 811 / 923 s singlefileunchanged (singlefile; seeds the cache, once per host)040716049919, 40735213435, 40709255352
Blacksmith fallback (cache off)537 s singlefilewhole-module (expected about the same)~040729029641

The "after" numbers are expected values; I'll post the first measured instance-mode whole-module build on this PR. Three of today's four cold builds were hosts' first host-mode builds, the first landings after the per-host tree. Those are a one-time seed per host and stay singlefile on purpose. Seeding whole-module would only move the 808 s cold build to the host's next landing and add the ~545 s build in front of it. The case this PR fixes recurs: a landing whose host root is busy.

Follow-up (not in this PR): partial caches. With 460-490 of 509 hits (the host cache a few landings behind), singlefile took 396 / 442 / 510 / 554 / 557 / 738 s, median 532 s, about the same as whole-module (40724663782, 40726640916, 40706172149, 40736642083, 40713069376, 40711035956). Those stay singlefile so they keep the cache current. The fix there is affinity routing: place the landing on the host whose cache is nearest.

Tests

tests/test_cmux_next_dev_release_host_root.py now runs the real script against stub compilers and checks the mode the compile sees in each case:

  • auto: a first host build and a later host build compile singlefile.
  • auto: cache off, off a mini, and a busy host root compile whole-module, with their log lines.
  • An explicit mode wins, and an unknown one exits 2 and releases the lock.

The workflow test checks that no || 'singlefile' default is left. test_cmux_next_dev_landing_workflow.py and test_cmux_next_hybrid_ref_capture.py pass unchanged.

CI proof

This PR changes no Swift. Swift test proof comes from the fleet, because GitHub's cmux-next swift test is cut short by its 300 s watchdog until #26809 lands.

  • Fleet, full CmuxNext suite, 6 shards (run-swift-testing-suites.sh, Xcode 26.6), at head 5085129d and at base 287054cd. Every head shard finished and printed its summary.

  • Isolated per-suite runs (package-test-lane.sh suite, one process per suite) for every suite the parallel run flagged:

  • No new SwiftUI is a base red with the same two findings (HomeChatColorsPanel.swift and HomeChatColorsView.swift, from the Chat Colors commits). Base run 40862042431 prints both and reports them as warning-only.

  • DiffRemoteWiringTests after #27389, in isolation at head 911c80de: passes (step 5da85ff10a957c39f004eb26).

  • cmux-next daemon tests at 8b9256fd, after #27385 fixed the signal 11: rerun job 123541420098 ran to its full summary (449 tests in 113 suites) with no crash. Its remaining reds match base per test: theFirstLaunchBringsEverythingOnce :49/:56 (order differs) and aDowngradeAndReUpgradeMergesOnlyNewWork :188, which fail the same way on base runs 40870840853, 40870846201, 40870863264, 40870868605 and 40881495139. This PR changes no Swift.

  • Swift test on GitHub is cut short by the watchdog (fixed separately); it is covered by the fleet proof above at this head.

  • Independent review: gpt-6-astra (high, fast) gave SHIP at 8b9256fd.

Risk

Low. Only the mode of instance-mode DEV compiles changes, and it goes back to the mode every DEV compile used before #24994. Host builds compile exactly as before. The product-reuse key changes once, so the first landing after merge compiles instead of reusing.

Rollback

Set the repo variable CMUX_NEXT_DEV_SWIFT_COMPILATION_MODE=singlefile to get today's behavior on every build (or wholemodule for every build). Or revert this PR.

Agent session: agent session

šŸ¤– Generated with Claude Code