Skip to content

Commit 82afac8

Browse files
authored
fix: iOS CI performance — hashFiles caching, macOS-26 runner, xcbeautify (#919)
1 parent 9cd067e commit 82afac8

2 files changed

Lines changed: 32 additions & 39 deletions

File tree

.claude/rules/ci-caching.xml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,25 @@
3838
</rule>
3939

4040
<rule severity="CRITICAL" enforcement="BLOCKING">
41-
<name>Cache Key Design — Immutable Keys</name>
42-
<description>GitHub Actions caches are IMMUTABLE — cache/save fails if the key already exists</description>
41+
<name>Cache Key Design — Content-Addressed Keys</name>
42+
<description>Use hashFiles() for cache keys so identical inputs produce cache hits</description>
4343
<problem>
44-
actions/cache/save cannot overwrite an existing cache entry. Using static keys
45-
(e.g. runner.os-gradle) means the first save succeeds, but every subsequent save
46-
fails with "Unable to reserve cache with key X, another job may be creating this cache."
47-
Caches become permanently stale.
44+
GitHub Actions caches are immutable — once saved, a key cannot be overwritten.
45+
Using github.run_id makes keys unique per run, so the primary key NEVER hits on
46+
restore. Every build falls through to restore-keys and gets a stale cache that
47+
xcodebuild/Gradle must then re-validate, which can be slower than a clean build.
4848
</problem>
4949
<guidelines>
50-
<guideline>Save key MUST be unique per run: prefix-${{ github.run_id }}</guideline>
51-
<guideline>Restore uses exact key + restore-keys prefix fallback to get the latest</guideline>
50+
<guideline>Use hashFiles() of lock files for cache keys (Podfile.lock, Gemfile.lock, bun.lock)</guideline>
51+
<guideline>Include Xcode version in DerivedData key suffix (e.g. -xcode26.2)</guideline>
52+
<guideline>Use actions/cache@v5 (unified) instead of separate cache/restore + cache/save</guideline>
53+
<guideline>Use restore-keys for DerivedData (partial hits useful), NOT for Pods (stale Pods break builds)</guideline>
5254
<guideline>Don't use version suffixes (v2, v3) — purge with gh cache delete --all</guideline>
5355
<guideline>Old cache entries are evicted automatically by GitHub's LRU policy</guideline>
5456
</guidelines>
5557
<example>
56-
Restore: key=runner.os-gradle-${{ github.run_id }}, restore-keys=runner.os-gradle-
57-
Save: key=runner.os-gradle-${{ github.run_id }}
58+
Pods: key=runner.os-pods-${{ hashFiles('example/ios/Podfile.lock', 'example/Gemfile.lock') }}
59+
DD: key=runner.os-dd-${{ hashFiles('...lockfiles...') }}-xcode26.2, restore-keys=runner.os-dd-
5860
</example>
5961
</rule>
6062

.github/workflows/e2e-ios-test.yml

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ on:
2525

2626
jobs:
2727
e2e-tests-ios:
28-
runs-on: macos-15
28+
runs-on: macOS-26
2929
env:
3030
OUTPUT_DIR: ~/output
3131
USE_CCACHE: '1'
@@ -41,6 +41,9 @@ jobs:
4141
sudo xcode-select -s "/Applications/Xcode_26.2.app/Contents/Developer"
4242
xcodebuild -version
4343
44+
- name: Install xcbeautify
45+
run: brew install xcbeautify
46+
4447
- name: Setup ccache
4548
uses: hendrikmuhs/ccache-action@v1.2
4649
with:
@@ -75,24 +78,23 @@ jobs:
7578

7679
- name: Restore CocoaPods cache
7780
id: pods-cache
78-
uses: actions/cache/restore@v5
81+
uses: actions/cache@v5
7982
with:
8083
path: |
8184
example/ios/Pods
8285
~/.cocoapods/repos
8386
~/Library/Caches/CocoaPods
8487
packages/react-native-quick-crypto/ios/libsodium-stable
8588
packages/react-native-quick-crypto/deps
86-
key: ${{ runner.os }}-pods-${{ github.run_id }}
87-
restore-keys: |
88-
${{ runner.os }}-pods-
89+
packages/react-native-quick-crypto/OpenSSL.xcframework
90+
key: ${{ runner.os }}-pods-${{ hashFiles('example/ios/Podfile.lock', 'example/Gemfile.lock') }}
8991

9092
- name: Restore DerivedData cache
9193
id: dd-cache
92-
uses: actions/cache/restore@v5
94+
uses: actions/cache@v5
9395
with:
9496
path: ${{ env.DERIVED_DATA_PATH }}
95-
key: ${{ runner.os }}-dd-${{ github.run_id }}
97+
key: ${{ runner.os }}-dd-${{ hashFiles('example/ios/Podfile.lock', 'example/Gemfile.lock', 'bun.lock') }}-xcode26.2
9698
restore-keys: |
9799
${{ runner.os }}-dd-
98100
@@ -102,24 +104,32 @@ jobs:
102104
103105
- name: Install CocoaPods
104106
working-directory: ./example
105-
run: bun pods
107+
run: RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1 bundle exec pod install --project-directory=ios
106108

107109
- name: Build iOS App
108110
working-directory: ./example
109111
run: |
110-
echo "Building iOS app with xcodebuild..."
111112
set -o pipefail
112113
xcodebuild \
113114
CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ \
114115
-derivedDataPath ios/build \
116+
-UseModernBuildSystem=YES \
115117
-workspace ios/QuickCryptoExample.xcworkspace \
116118
-scheme QuickCryptoExample \
117119
-sdk iphonesimulator \
118120
-configuration Debug \
119121
-destination "platform=iOS Simulator,name=${{ env.DEVICE_NAME }}" \
122+
-showBuildTimingSummary \
120123
ONLY_ACTIVE_ARCH=YES \
121124
CODE_SIGNING_ALLOWED=NO \
122-
build > $HOME/output/ios-build.log 2>&1
125+
build 2>&1 | tee $HOME/output/ios-build.log | xcbeautify --renderer github-actions
126+
127+
- name: Show Build Timing Summary
128+
if: always()
129+
run: |
130+
if [ -f "$HOME/output/ios-build.log" ]; then
131+
sed -n '/Build Timing Summary/,/^\*\* BUILD/p' "$HOME/output/ios-build.log"
132+
fi
123133
124134
- name: Show ccache stats
125135
if: always()
@@ -229,25 +239,6 @@ jobs:
229239
${{ env.OUTPUT_DIR }}/screenshots/*.png
230240
retention-days: 5
231241

232-
- name: Save CocoaPods cache
233-
if: always()
234-
uses: actions/cache/save@v5
235-
with:
236-
path: |
237-
example/ios/Pods
238-
~/.cocoapods/repos
239-
~/Library/Caches/CocoaPods
240-
packages/react-native-quick-crypto/ios/libsodium-stable
241-
packages/react-native-quick-crypto/deps
242-
key: ${{ runner.os }}-pods-${{ github.run_id }}
243-
244-
- name: Save DerivedData cache
245-
if: always()
246-
uses: actions/cache/save@v5
247-
with:
248-
path: ${{ env.DERIVED_DATA_PATH }}
249-
key: ${{ runner.os }}-dd-${{ github.run_id }}
250-
251242
- name: Exit with Test Result
252243
if: always()
253244
run: |

0 commit comments

Comments
 (0)