From ae6a0b41dc5db2e209709da17da025045549f6a4 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Thu, 13 Aug 2026 09:37:12 +0100 Subject: [PATCH] Cache DerivedData on the macOS build jobs and stop cleaning it away MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of the macOS CI speed-up. Tracked on shop/issues-checkout-kit#1210, under shop/issues-checkout-kit#1202. ## The two halves only work together `clean` deletes nothing on a fresh runner, so removing it alone changes nothing. A DerivedData cache saves nothing while a `clean` runs before every build. Paired here on purpose, and they report one delta. ## Change **Stop cleaning** - `platforms/react-native/sample/scripts/build_ios` — `xcodebuild build`, was `xcodebuild clean build`. - `platforms/swift/Scripts/build_and_test_samples:30` — `"build test"`, was `"clean build test"`. This is the line the first PR in this stack deliberately left alone, so that its own measurement moved one variable. On a fresh runner it deletes nothing, so it belongs here, beside the cache it would otherwise wipe. Merge order matters: the #632 delta has to be read before this lands, or the two changes share one number. **Start caching** Two `restore-cache@3` / `save-cache@1` pairs over `~/Library/Developer/Xcode/DerivedData`, one per macOS build workflow, keyed on the resolved dependency graph: | Workflow | Cache key inputs | | --- | --- | | `ci-ios-swift-samples` | `Package.resolved` | | `ci-ios-react-native-build-ios` | `sample/ios/Podfile.lock` + `Package.resolved` | Separate keys, because a shared key would have each job overwrite the other's products. Both jobs spend most of their time compiling dependencies that do not change between pull requests — Apollo iOS and the root Swift package for the Swift samples, the 89 React Native New Architecture C++ pods for the sample app. A pin change rebuilds rather than reuses. Xcode's incremental build owns correctness for the source that did change, which is its normal mode locally. Each `restore-cache@3` has a matching `save-cache@1`. A restore with no save warms nothing: the key never gets written, so every build pays full price while looking cached. Developer-only entry points such as `platforms/swift/Scripts/build_samples` keep their `clean`. A stale local DerivedData tree is a real failure mode there, and the rebuild costs nobody CI time. ## Measure the *second* run after merge A cold cache shows nothing. The number that matters is the median over 10 `main` runs once the key is populated. | Job | baseline median | baseline slowest step | | --- | --- | --- | | `React Native / Build iOS Sample` | 11m59s | `Build iOS sample` 10m27s | | `Swift / build-and-test-samples` | 6m38s | `Run Tests` 6m04s | ## Verification - `shadowenv exec -- ./scripts/test_ruby` — green. - `shadowenv exec -- bitrise validate --config=e2e/bitrise.yml` — valid. - Cold and warm local builds of the React Native sample with `CI=true`. ## 🔴 Unrelated local breakage found while verifying `pnpm sample build:ios` fails on a developer machine that has `sccache` on `PATH`: ``` error: unable to spawn process 'sccache clang' (No such file or directory) ``` `build_ios:8-18` exports `CC="sccache clang"`, and Xcode cannot spawn a two-word command as a compiler. CI is unaffected — the `CI = true` branch skips sccache entirely. The next PR in this stack touches these lines, so it is recorded on shop/issues-checkout-kit#1211 rather than fixed here. ## Decision made without you The plan scoped this to the React Native sample. It is applied to `ci-ios-swift-samples` too, because that job's `clean` has the same effect on a restored cache and leaving it in would make the new invariant untrue for half the macOS fleet. --- e2e/bitrise.yml | 22 +++++++++++++++++++ .../react-native/sample/scripts/build_ios | 2 +- .../swift/Scripts/build_and_test_samples | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/e2e/bitrise.yml b/e2e/bitrise.yml index eff063f35..e9699875a 100644 --- a/e2e/bitrise.yml +++ b/e2e/bitrise.yml @@ -334,6 +334,14 @@ workflows: - bundle::bootstrap-mint: inputs: - mintfile_dir: platforms/swift + # Both samples build the root package and Apollo iOS from source. Neither changes + # between most pull requests, so DerivedData carries that work across builds. The key + # holds the resolved dependency graph: a pin change rebuilds rather than reuses. + # Xcode's incremental build owns correctness for the source that did change. + - restore-cache@3: + inputs: + - key: &swift_samples_derived_data_cache_key |- + swift-samples-derived-data-{{ .OS }}-{{ .Arch }}-{{ checksum "Package.resolved" }} - script@1: title: Build and test the Swift sample apps timeout: 5400 @@ -350,6 +358,10 @@ workflows: ./scripts/setup_storefront_env --skip-optional-prompts cd platforms/swift ./Scripts/build_and_test_samples + - save-cache@1: + inputs: + - key: *swift_samples_derived_data_cache_key + - paths: ~/Library/Developer/Xcode/DerivedData ci-ios-react-native-build-ios: meta: @@ -373,6 +385,12 @@ workflows: - bundle::install-node-modules: {} - bundle::install-ruby-gems: {} - bundle::install-cocoapods: {} + # The 89 pods here are the React Native New Architecture C++ core. They dominate the + # build and only change when the lockfile or the Swift package pins do. + - restore-cache@3: + inputs: + - key: &rn_sample_derived_data_cache_key |- + rn-sample-derived-data-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}-{{ checksum "Package.resolved" }} - script@1: title: Build the React Native iOS sample timeout: 5400 @@ -383,6 +401,10 @@ workflows: cd platforms/react-native pnpm module build pnpm sample build:ios + - save-cache@1: + inputs: + - key: *rn_sample_derived_data_cache_key + - paths: ~/Library/Developer/Xcode/DerivedData ci-ios-react-native-test-ios: meta: diff --git a/platforms/react-native/sample/scripts/build_ios b/platforms/react-native/sample/scripts/build_ios index 6dcef74a8..df5aae42a 100755 --- a/platforms/react-native/sample/scripts/build_ios +++ b/platforms/react-native/sample/scripts/build_ios @@ -30,7 +30,7 @@ if [ "${GITHUB_ACTIONS:-}" = "true" ]; then fi run_xcodebuild() { - xcodebuild clean build \ + xcodebuild build \ -workspace CheckoutKitReactNativeDemo.xcworkspace \ -scheme CheckoutKitReactNativeDemo \ -sdk iphonesimulator \ diff --git a/platforms/swift/Scripts/build_and_test_samples b/platforms/swift/Scripts/build_and_test_samples index f3c59611b..a628f76c0 100755 --- a/platforms/swift/Scripts/build_and_test_samples +++ b/platforms/swift/Scripts/build_and_test_samples @@ -27,5 +27,5 @@ run_app() { "$SCRIPT_DIR/xcode_run" "$action" "$app" } -run_app CheckoutKitSwiftDemo "clean build test" +run_app CheckoutKitSwiftDemo "build test" run_app ShopifyAcceleratedCheckoutsApp "build"