From a28574f0d484e0c9d0f1c447b3b1e7cbd45fd396 Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Sat, 21 Feb 2026 08:38:58 +0100 Subject: [PATCH 1/4] [ci] Add restore ccache to expo-caches (#43273) # Why This is a base for the following PRs that introduce the implementation of ccache for iOS and Android builds: https://github.com/expo/expo/pull/43274 https://github.com/expo/expo/pull/43285 It adds a new cache to `expo-caches`, which will be used there. # How Adds a new `Restore ccache` step to `expo-caches`. The key for this cache consists of: - `yarn.lock` - to follow changes in `node_modules` - `packages/**/*.cpp`, `packages/**/*.h`, `packages/**/*.mm`, `packages/**/*.m` - these files are cached by ccache A `restore-key` was added because the hash doesn't have to match exactly and the cache can be partially reused. # Test Plan Green CI, tested on a stacked PR. --- .github/actions/expo-caches/action.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/actions/expo-caches/action.yml b/.github/actions/expo-caches/action.yml index bec85cc4c91cab..5d0e7e7cfe259b 100644 --- a/.github/actions/expo-caches/action.yml +++ b/.github/actions/expo-caches/action.yml @@ -36,6 +36,9 @@ inputs: description: 'NDK version used' default: '23.1.7779620' required: false + ccache: + description: 'Restore ccache' + required: false git-lfs: description: 'Restore Git LFS cache' required: false @@ -191,6 +194,14 @@ runs: shell: bash run: sudo $ANDROID_SDK_ROOT/tools/bin/sdkmanager --install "ndk;${{ inputs.ndk-version }}" + - name: ♻️ Restore ccache + if: inputs.ccache == 'true' + uses: actions/cache@v4 + with: + path: ${{ runner.temp }}/.ccache + key: ${{ runner.os }}-ccache-${{ hashFiles('yarn.lock', 'packages/**/*.cpp', 'packages/**/*.h', 'packages/**/*.mm', 'packages/**/*.m') }} + restore-keys: ${{ runner.os }}-ccache- + - name: 🔍️ Get cache key of Git LFS files if: inputs.git-lfs == 'true' id: git-lfs From 762353f60194bbc03056c4b174f79d42f67357fb Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Sat, 21 Feb 2026 08:40:10 +0100 Subject: [PATCH 2/4] [ci][android] Optimize build time with ccache (#43274) # Why This PR introduces ccache for Android builds. It speeds up the most demanding task for Android builds, which is cpp compilation. # Results - **Hit rate: 87%** - that's the percentage of c++ files which didn't have to be compiled. - **Build time drop:** My tests showed a **4-minute** reduction in build time. This time varies based on runner performance, so the most precise numbers will be visible after some time in our performance metrics. # How - Downloads ccache. - Replaces the default compiler with ccache. - Adds custom configuration for ccache, which is described in the comments. # Test Plan Green CI, tested on a stacked PR. --- .github/workflows/test-suite.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 3f3fde9f11cec4..06dfe977fc7519 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -340,16 +340,45 @@ jobs: with: distribution: 'temurin' java-version: '17' + - name: Install ccache + run: | + sudo apt-get update + sudo apt-get install -y ccache + - name: Configure ccache + run: | + # It must be the same as in .github/actions/expo-caches/action.yml + echo "CCACHE_DIR=${{ runner.temp }}/.ccache" >> $GITHUB_ENV + + # By default ccache includes mtime of a compiler in hashes, for each CI run mtime varies. + echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV + + # Calculate hash based on a relative path - this prevents cache misses when an absolute path changes. + echo "CCACHE_BASEDIR=${{ github.workspace }}" >> $GITHUB_ENV + + # Default is 5GB, this cache takes only ~300MB. + echo "CCACHE_MAXSIZE=1G" >> $GITHUB_ENV + + # Sloppiness options disable some of the ccache checks to increase hit rate. + # In our case we exclude ctime and mtime so cache is hit based on file content. + # time_macros might help if in included modules there are macros like __TIME__ which would trigger a cache miss. + echo "CCACHE_SLOPPINESS=include_file_ctime,include_file_mtime,time_macros" >> $GITHUB_ENV + + # Replaces compiler with ccache + echo "CMAKE_C_COMPILER_LAUNCHER=$(which ccache)" >> $GITHUB_ENV + echo "CMAKE_CXX_COMPILER_LAUNCHER=$(which ccache)" >> $GITHUB_ENV - name: ➕ Add `bin` to GITHUB_PATH run: echo "$(pwd)/bin" >> $GITHUB_PATH - name: ♻️ Restore caches uses: ./.github/actions/expo-caches id: expo-caches with: + ccache: 'true' gradle: 'true' yarn-workspace: 'true' yarn-tools: 'true' react-native-gradle-downloads: 'true' + - name: Reset ccache stats + run: ccache -z - name: 🧶 Install workspace node modules run: yarn install --frozen-lockfile - name: 🧶 Install image comparison server node modules @@ -392,6 +421,8 @@ jobs: ./scripts/start-android-e2e-test.ts --build build-output: apps/bare-expo/android/app/build/outputs/apk/release artifact-name: bare-expo-android-builds + - name: Show ccache stats + run: ccache -s -v - name: 🔔 Notify on Slack uses: ./.github/actions/slack-notify if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-')) From d8c88e9d3ff89ddff11336ff918662c7a9aaea11 Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Sat, 21 Feb 2026 08:40:26 +0100 Subject: [PATCH 3/4] [ci][iOS] Optimize build time with ccache (#43285) # Why This PR introduces ccache for iOS builds. It speeds up the compilation of .cpp, .mm, and .m files. # Results - **Hit rate for subsequent clean builds: ~100% (2436 files)** the percentage of files that didn't have to be compiled. - **Drop in build time:** my tests showed around a **10-minute** reduction. This time varies based on runner performance, so more precise numbers will be available later in performance metrics. # How - Downloads ccache. - Forces Xcode to use ccache for compilation. - Adds custom configuration for ccache, described in the YAML file comments. # Test Plan Green CI, tested on a stacked PR. --- .github/workflows/test-suite.yml | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 06dfe977fc7519..0bc7a943eec492 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -152,13 +152,50 @@ jobs: with: bundler-cache: true ruby-version: 3.2.2 + - name: Setup ccache + run: | + brew install ccache + + # In order to use ccache with Xcode, unlike on Android, we can't just change env variables - we need to change PATH. + MKDIR_PATH=$HOME/.ccache_bin + mkdir -p $MKDIR_PATH + ln -sf $(which ccache) $MKDIR_PATH/clang + ln -sf $(which ccache) $MKDIR_PATH/clang++ + ln -sf $(which ccache) $MKDIR_PATH/cc + ln -sf $(which ccache) $MKDIR_PATH/c++ + echo "$MKDIR_PATH" >> $GITHUB_PATH + echo "CC=$MKDIR_PATH/clang" >> $GITHUB_ENV + echo "CXX=$MKDIR_PATH/clang++" >> $GITHUB_ENV + echo "LD=$MKDIR_PATH/clang++" >> $GITHUB_ENV + echo "LDPLUSPLUS=$MKDIR_PATH/clang++" >> $GITHUB_ENV + + # By default ccache includes mtime of a compiler in hashes, for each CI run mtime varies. + echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV + + # It must be the same as in .github/actions/expo-caches/action.yml + echo "CCACHE_DIR=${{ runner.temp }}/.ccache" >> $GITHUB_ENV + + # Sloppiness options disable some of the ccache checks to increase hit rate. + # We exclude ctime and mtime so the cache is hit based on file content. + # time_macros might help if in included modules there are macros like __TIME__ which would trigger a cache miss. + # modules, clang_index_store, system_headers, and ivfsoverlay are Xcode-specific options. + echo "CCACHE_SLOPPINESS=include_file_mtime,include_file_ctime,time_macros,modules,clang_index_store,system_headers,ivfsoverlay" >> $GITHUB_ENV + + # Speeds up the process on cache misses by skipping the preprocessing step. + echo "CCACHE_DEPEND=true" >> $GITHUB_ENV + + # Speeds up copying files on cache hits; natively supported by macOS APFS. + echo "CCACHE_FILECLONE=true" >> $GITHUB_ENV - name: ♻️ Restore caches uses: ./.github/actions/expo-caches id: expo-caches with: + ccache: 'true' yarn-workspace: 'true' yarn-tools: 'true' bare-expo-pods: 'true' + - name: Reset ccache stats + run: ccache -z - name: 🧶 Install node modules in root dir run: yarn install --frozen-lockfile - name: 🕵️ Debug CocoaPods lockfiles @@ -205,6 +242,8 @@ jobs: ./scripts/start-ios-e2e-test.ts --build build-output: apps/bare-expo/ios/build/BareExpo.app artifact-name: bare-expo-ios-builds + - name: Show ccache stats + run: ccache -s -v - name: 🔔 Notify on Slack uses: ./.github/actions/slack-notify if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-')) From 4772866c7ffbae879d96cec31fed78fafc266f65 Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Sat, 21 Feb 2026 10:31:19 +0100 Subject: [PATCH 4/4] [ci] Ignore create-expo scripts in test suite (#43306) # Why Changes to the following packages: - create-expo - create-expo-nightly - create-expo-module - @expo/cli shouldn't trigger the test suite # How Adds new negations to paths which trigger the workflow and to `common-paths` in `Detect build-relevant platform changes` # Test Plan Green CI --- .github/workflows/test-suite.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 0bc7a943eec492..2db1d1639cf33d 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -13,6 +13,9 @@ on: - packages/** - yarn.lock - '!packages/@expo/cli/**' + - '!packages/create-expo/**' + - '!packages/create-expo-nightly/**' + - '!packages/create-expo-module/**' - '!**.md' - '!**/__tests__/**' - '!**/__mocks__/**' @@ -26,6 +29,9 @@ on: - packages/** - yarn.lock - '!packages/@expo/cli/**' + - '!packages/create-expo/**' + - '!packages/create-expo-nightly/**' + - '!packages/create-expo-module/**' - '!**.md' - '!**/__tests__/**' - '!**/__mocks__/**' @@ -63,6 +69,8 @@ jobs: "apps/bare-expo/**", "apps/test-suite/**", "packages/**", + "!packages/@expo/cli/**", + "!packages/{create-expo,create-expo-nightly,create-expo-module}/**", yarn.lock, "!packages/**/{ios,android}/**", "!apps/bare-expo/**/{ios,android}/**"