Enable ccache for the React Native pods on CI - #638
Draft
kieran-osgood-shopify wants to merge 1 commit into
Draft
Conversation
This was referenced Aug 13, 2026
Contributor
Author
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
11 tasks
11 tasks
kieran-osgood-shopify
force-pushed
the
kieran-osgood/macos-ci-9-ccache
branch
from
August 13, 2026 09:22
1780924 to
e8b55e9
Compare
kieran-osgood-shopify
force-pushed
the
kieran-osgood/macos-ci-8-derived-data-cache
branch
from
August 13, 2026 09:22
ae182ae to
1dd7a03
Compare
Part of the macOS CI speed-up. Tracked on shop/issues-checkout-kit#1211, under shop/issues-checkout-kit#1202. Lands alone, on purpose. Compiler wrappers can miscompile silently, so this soaks by itself before the next optimisation goes near the same builds. ## Change **Opt-in, never a default** Both Podfiles read `CCACHE_ENABLED` rather than hardcoding a value: - `platforms/react-native/sample/ios/Podfile` - `platforms/react-native/test/rct-integration-app/Podfile` `e2e/bitrise.yml` sets `CCACHE_ENABLED: "1"` on the two React Native macOS workflows and nowhere else. Keeping it an explicit environment choice is the same rule `POD_REPO_UPDATE` follows: a hardcoded default makes local runs stop matching CI. `react_native_post_install` bakes the wrapper into the generated Pods project at `pod install` time, not at build time. So the flag has to be set for both, and the install step runs after ccache is on `PATH`. Both `Podfile.lock` files change in one line, `PODFILE CHECKSUM`, because the Podfile text changed. The lock is byte-identical with and without `CCACHE_ENABLED=1` — verified below — so CI's `pod install --deployment` stays happy on either path. **🔴 A ccache `pod install` dirties a tracked Xcode project** React Native writes the wrapper into the app project too, including an absolute `CCACHE_BINARY = /opt/homebrew/bin/ccache`, and `sample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxproj` is committed. A later plain `pod install` does not reliably put it back. 🟡 So check `git status` after a ccache `pod install`, and restore that file before you commit. `CONTRIBUTING.md` says how. Nothing on Bitrise reads it, so a dirty CI checkout is harmless there. **Cache the compiler cache** An inline `restore-cache@3` / `save-cache@1` pair per workflow over the ccache directory, keyed by branch and commit with branch-then-arch fallback prefixes. Without it the wrapper starts cold on every build and buys nothing. 🟡 Two traps to know if you edit these blocks: - `restore-cache` accepts exactly one `key` input, holding a priority-ordered list. A plural `keys:` reads naturally, does not exist, and `bitrise validate` does not catch it — the step fails at runtime instead. I wrote it that way first. - `save-cache` takes a single key, so the first line of the restore key is repeated by hand. The two copies have to stay identical. **🔴 The obvious version of this change caches nothing** `pod install` points `CC` at React Native's `ccache-clang.sh`, which runs `exec $CCACHE_BINARY clang`. It writes `CCACHE_BINARY` as an Xcode *build setting*, and Xcode does not put build settings in the compiler's environment. The wrapper therefore execs a bare `clang`. The build succeeds, the tests pass, and the hit rate is zero — measured, not guessed: | Build | ccache calls | hits | misses | | --- | --- | --- | --- | | Podfile flag only | 0 | 0 | 0 | | Plus `CCACHE_BINARY` exported | 3 | 0 | 3 | | Same again, warm | 3 | 3 | 0 | So `build_ios` and `test_ios` export it when `CCACHE_ENABLED=1`, and `scripts/test/ccache_opt_in_test.rb` fails if either stops. A silent no-op that still goes green is exactly the failure a comment does not catch. **Stop fighting over the compiler** `platforms/react-native/sample/scripts/build_ios` no longer exports an sccache compiler wrapper at all. It could never have worked (see below), and the ccache settings the Podfile writes are Xcode build settings, which win over shell exports anyway. ## 🔴 Also fixes a local-only breakage `pnpm sample build:ios` fails today on any developer machine with `sccache` on `PATH`: ``` error: unable to spawn process 'sccache clang' (No such file or directory) ``` `build_ios` exported `CC="sccache clang"`, and Xcode spawns `CC` as a single executable, so a two-word value can never resolve. CI never hit this because the `CI = true` branch skipped sccache. Reproduced on this branch before the change and green after. Only Android uses sccache now. `platforms/react-native/sample/scripts/android_sccache` shims it behind the name CMake looks for, which is the pattern Xcode has no equivalent of. `platforms/react-native/CONTRIBUTING.md` is updated to say so. **Docs** `e2e/BITRISE.md` gains a paragraph on the two `ci-ios` caches — DerivedData from the previous PR and ccache from this one — including the `key` versus `keys` trap above. It lands here rather than in a docs-only PR, because both caches exist by this point in the stack and not before it. ## Baselines to beat | Job | median | slowest step | | --- | --- | --- | | `React Native / Build iOS Sample` | 11m59s | `Build iOS sample` 10m27s | | `React Native / Run iOS Tests` | 13m19s | `Run iOS tests` 11m44s | Measure the second run after merge, once the ccache key is populated. ## Verification - `shadowenv exec -- ./scripts/test_ruby` — 300 runs, 715 assertions, 0 failures. - `shadowenv exec -- bitrise validate --config=e2e/bitrise.yml` — `Config is valid: true`. - `bash -n platforms/react-native/sample/scripts/build_ios` — clean. - `CCACHE_ENABLED=1 pod install` prints `[Ccache]: Setting CC, LD, CXX & LDPLUSPLUS build settings` and writes 10 ccache references into `Pods.xcodeproj`. A plain `pod install` writes zero. Both produce the same `Podfile.lock`. - Local builds of the React Native sample, cold and warm, with and without `CCACHE_ENABLED=1`. All `Build Succeeded`; hit rates in the table above. - `.ccache/` is gitignored, because `CCACHE_DIR` sits under the source directory so `save-cache` can reach it. ## Decisions made without you - The guard test skips comment lines. Otherwise the comment explaining the variable trips the invariant that forbids setting it. - `e2e/bitrise.yml` is deliberately outside the guard's globs. It is the one file allowed to turn ccache on. ## Open question for the author The Bitrise macOS stack may not ship `ccache`. This PR installs it with a `command -v` guard, the same shape `bootstrap-mint` uses for `mint`. If the stack already carries it, the guard is a no-op and the step can be dropped.
kieran-osgood-shopify
force-pushed
the
kieran-osgood/macos-ci-9-ccache
branch
from
August 13, 2026 11:36
e8b55e9 to
af6792e
Compare
kieran-osgood-shopify
force-pushed
the
kieran-osgood/macos-ci-8-derived-data-cache
branch
from
August 13, 2026 11:36
1dd7a03 to
4f7cff0
Compare
Contributor
|
yo, just browsing.. Should we use sccache to match up with what we run locally? |
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.

Part of the macOS CI speed-up. Tracked on shop/issues-checkout-kit#1211, under shop/issues-checkout-kit#1202.
Lands alone, on purpose. Compiler wrappers can miscompile silently, so this
soaks by itself before the next optimisation goes near the same builds.
Change
Opt-in, never a default
Both Podfiles read
CCACHE_ENABLEDrather than hardcoding a value:platforms/react-native/sample/ios/Podfileplatforms/react-native/test/rct-integration-app/Podfilee2e/bitrise.ymlsetsCCACHE_ENABLED: "1"on the two React Native macOSworkflows and nowhere else. Keeping it an explicit environment choice is the same
rule
POD_REPO_UPDATEfollows: a hardcoded default makes local runs stopmatching CI.
react_native_post_installbakes the wrapper into the generated Pods project atpod installtime, not at build time. So the flag has to be set for both, andthe install step runs after ccache is on
PATH.Both
Podfile.lockfiles change in one line,PODFILE CHECKSUM, because thePodfile text changed. The lock is byte-identical with and without
CCACHE_ENABLED=1— verified below — so CI'spod install --deploymentstayshappy on either path.
🔴 A ccache
pod installdirties a tracked Xcode projectReact Native writes the wrapper into the app project too, including an absolute
CCACHE_BINARY = /opt/homebrew/bin/ccache, andsample/ios/CheckoutKitReactNativeDemo.xcodeproj/project.pbxprojis committed. Alater plain
pod installdoes not reliably put it back.scripts/test/ccache_opt_in_test.rbnow fails if that project carries a compilerwrapper, so the machine-specific path cannot reach
main.CONTRIBUTING.mdsayshow to restore it. Nothing on Bitrise reads the file, so a dirty CI checkout is
harmless there.
Cache the compiler cache
An inline
restore-cache@3/save-cache@1pair per workflow over the ccachedirectory, keyed by branch and commit with branch-then-arch fallback prefixes.
Without it the wrapper starts cold on every build and buys nothing.
Two new invariants in
e2e/test/bitrise_config_test.rbguard the shape:restore-cacheaccepts exactly onekeyinput, holding a priority-orderedlist. A plural
keys:reads naturally, does not exist, andbitrise validatedoes not catch it — the step fails at runtime instead. I wrote it that way
first, so the test now spells out the four inputs the step declares.
save-cachetakes a single key, so the exact key is repeated by hand. Theexisting restore-versus-save pairing check now compares only the first line,
which makes drift between the two copies a test failure.
🔴 The obvious version of this change caches nothing
pod installpointsCCat React Native'sccache-clang.sh, which runsexec $CCACHE_BINARY clang. It writesCCACHE_BINARYas an Xcode buildsetting, and Xcode does not put build settings in the compiler's environment.
The wrapper therefore execs a bare
clang. The build succeeds, the tests pass,and the hit rate is zero — measured, not guessed:
CCACHE_BINARYexportedSo
build_iosandtest_iosexport it whenCCACHE_ENABLED=1, andscripts/test/ccache_opt_in_test.rbfails if either stops. A silent no-op thatstill goes green is exactly the failure a comment does not catch.
Stop fighting over the compiler
platforms/react-native/sample/scripts/build_iosno longer exports an sccachecompiler wrapper at all. It could never have worked (see below), and the ccache
settings the Podfile writes are Xcode build settings, which win over shell
exports anyway.
🔴 Also fixes a local-only breakage
pnpm sample build:iosfails today on any developer machine withsccacheonPATH:build_iosexportedCC="sccache clang", and Xcode spawnsCCas a singleexecutable, so a two-word value can never resolve. CI never hit this because the
CI = truebranch skipped sccache. Reproduced on this branch before the changeand green after.
Only Android uses sccache now.
platforms/react-native/sample/scripts/android_sccacheshims it behind the name CMake looks for, which is the pattern Xcode has no
equivalent of.
platforms/react-native/CONTRIBUTING.mdis updated to say so.Baselines to beat
React Native / Build iOS SampleBuild iOS sample10m27sReact Native / Run iOS TestsRun iOS tests11m44sMeasure the second run after merge, once the ccache key is populated.
Verification
shadowenv exec -- ./scripts/test_ruby— 300 runs, 715 assertions, 0 failures.shadowenv exec -- bitrise validate --config=e2e/bitrise.yml—Config is valid: true.bash -n platforms/react-native/sample/scripts/build_ios— clean.CCACHE_ENABLED=1 pod installprints[Ccache]: Setting CC, LD, CXX & LDPLUSPLUS build settingsand writes 10 ccache references intoPods.xcodeproj. A plainpod installwrites zero. Both produce the samePodfile.lock.CCACHE_ENABLED=1. AllBuild Succeeded; hit rates in the table above..ccache/is gitignored, becauseCCACHE_DIRsits under the source directoryso
save-cachecan reach it.Decisions made without you
variable trips the invariant that forbids setting it.
e2e/bitrise.ymlis deliberately outside the guard's globs. It is the one fileallowed to turn ccache on.
Open question for the author
The Bitrise macOS stack may not ship
ccache. This PR installs it with acommand -vguard, the same shapebootstrap-mintuses formint. If the stackalready carries it, the guard is a no-op and the step can be dropped.
Before you merge
Important
platforms/swift/README.mdand/orplatforms/android/README.md)Releasing a new Swift version?
ShopifyCheckoutKit.podspecplatforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swiftplatforms/swift/README.md(major version only)Releasing a new Embedded Checkout Protocol version?
embeddedCheckoutProtocolAndroidinplatforms/android/gradle/libs.versions.tomlprotocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.apiif the public API changedReleasing a new Android version?
checkoutKitAndroidinplatforms/android/gradle/libs.versions.tomlplatforms/android/README.mdTip
See the Contributing documentation for the full release process per platform.