Parallelize the Test workflow into three cached jobs#38
Merged
Conversation
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>
4a81ecc to
8871518
Compare
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Splits the single sequential
testjob in.github/workflows/test.ymlinto three jobs that run in parallel —package(KVMCore SwiftPM tests),macos, andipad— 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 justDerivedData/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. Thepackagejob keeps a toolchain-keyed SwiftPM.buildcache; 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.