Skip to content

Parallelize the Test workflow into three cached jobs#38

Merged
thirteen37 merged 6 commits into
mainfrom
thirteen37/speed-up-test-actions
Jun 26, 2026
Merged

Parallelize the Test workflow into three cached jobs#38
thirteen37 merged 6 commits into
mainfrom
thirteen37/speed-up-test-actions

Conversation

@thirteen37

@thirteen37 thirteen37 commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Splits the single sequential test job in .github/workflows/test.yml into three jobs that run in parallel — package (KVMCore SwiftPM tests), macos, and ipad — so wall-clock is the slowest job rather than the sum of all three. Cuts per-job setup by installing a pinned XcodeGen from its prebuilt release artifact instead of Homebrew (~2 min to a few seconds) and, on the iPad job, boots the simulator asynchronously at job start so it warms up off the critical path. Profiling showed the iPad job's remaining cost is the iOS-simulator build on a fresh runner (it builds the cold iOS-SDK module cache; locally the iOS build is no slower than macOS), so the iPad job caches just DerivedData/ModuleCache.noindex, keyed on the Xcode toolchain version alone since it is source-independent — an ~82 MB cache that restores in seconds and trims roughly two minutes off the build. The package job keeps a toolchain-keyed SwiftPM .build cache; the rest of DerivedData is intentionally not cached because Xcode rebuilds the project objects anyway (checkout resets mtimes) and the full cache was large and throttle-prone. The macOS and package jobs finish in well under a minute; the iPad job is bounded by the iOS build, which remains GitHub-runner-bound.

thirteen37 and others added 5 commits June 26, 2026 01:00
Split the single sequential test job into parallel package, macos, and
ipad jobs so wall-clock drops from sum(passes) to max(pass). Add SwiftPM
and DerivedData build caches, HOMEBREW_NO_AUTO_UPDATE for faster
xcodegen install, and xcodebuild speed flags (-quiet, -derivedDataPath,
-skipPackagePluginValidation, -skipMacroValidation,
COMPILER_INDEX_STORE_ENABLE=NO).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The iPad job's wall-clock is dominated by building the app for the
simulator plus the cold simulator boot. Boot the simulator at job start
so it warms up in parallel with the XcodeGen install and the build,
moving boot off the critical path.

Also fold a hash of `xcodebuild -version` into every cache key so a
runner-image Xcode roll invalidates the SwiftPM/DerivedData caches
instead of restoring stale module artifacts that fail with
"module compiled with a different version of Swift".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`brew install xcodegen` spends ~2 min per job in Homebrew overhead even
with HOMEBREW_NO_AUTO_UPDATE and a tiny 7.4 MB bottle. Download the
pinned prebuilt XcodeGen release artifact and add it to PATH instead,
cutting the install step from ~2 min to a few seconds on both the macos
and ipad jobs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`simctl boot` was blocking ~40-80s waiting for the device to finish
booting. The boot is actually carried out by the CoreSimulator daemon,
so run it via nohup in the background and let it complete while XcodeGen
installs and the app builds. If the device isn't ready when the test
step runs, xcodebuild boots it itself, so correctness is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The DerivedData cache grew to ~96 MB and GitHub's cache service throttled
the restore to ~0.1 MB/s, costing up to 2.5 min just to download it.
Xcode also rebuilds anyway because actions/checkout resets file mtimes,
so the cache saved little compile time while adding large, unpredictable
restore/save overhead. Remove it (and the now-unused -derivedDataPath and
toolchain-key steps) so the macOS and iPad jobs do a clean, predictable
build each run. The package job keeps its SwiftPM .build cache, which is
content-hashed, smaller, and reliably incremental.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thirteen37 thirteen37 force-pushed the thirteen37/speed-up-test-actions branch from 4a81ecc to 8871518 Compare June 25, 2026 18:52
Profiling showed the iPad job's extra CI time is not compilation (locally
the iOS build is as fast as macOS) but building the cold iOS-SDK Clang/
Swift module cache on a fresh runner. Cache just DerivedData/
ModuleCache.noindex (the 368 precompiled system-framework PCMs), keyed on
the Xcode toolchain version alone since it is source-independent, so it
restores on every run after the first. This skips the cold module build
without caching the rest of DerivedData, whose project objects Xcode
rebuilds anyway because checkout resets file mtimes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thirteen37 thirteen37 merged commit ba9aea8 into main Jun 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant