Skip to content

ci: put ccache in front of the Hermes build - #439

Open
kraenhansen wants to merge 2 commits into
nextfrom
claude/ci-green-state-3s04fe
Open

ci: put ccache in front of the Hermes build#439
kraenhansen wants to merge 2 commits into
nextfrom
claude/ci-green-state-3s04fe

Conversation

@kraenhansen

Copy link
Copy Markdown
Collaborator

Hermes is the iOS build. Everything else is rounding error.

Measured from the job logs of run 31691647982:

Phase Duration Share of Build test app
[RN] [1] Build Hermesc 3m40s 19%
[RN] [2] Build Hermes 13m35s 72%
Hermes total 17m15s 91%
Build test app step 18m58s 100%

It is not hidden concurrency: no CompileC or SwiftCompile task appears anywhere in the 13m35s [2] Build Hermes window — just ninja progress — and all 54 CompileC tasks in the entire build land in the final 66 seconds. Hermes serialises the build.

And ccache never saw any of it. C_COMPILER_LAUNCHER is an Xcode build setting, whereas Hermes is built by CMake from a script phase, so the two never meet. That job logged 549 ccache calls in total, of which only 68 were cacheable — against 2190 calls / 1082 cacheable on a build that compiles React Native itself.

Approach

build-hermes-xcode.sh hard-codes its CMake flags and offers no hook for extra arguments, but it does not sanitise the environment — and CMake reads CMAKE_TOOLCHAIN_FILE from there. So a generated toolchain file setting CMAKE_C_COMPILER_LAUNCHER / CMAKE_CXX_COMPILER_LAUNCHER is enough to put ccache in front of Hermes' compiler without patching React Native.

Verified locally before pushing: with CMAKE_TOOLCHAIN_FILE present only in the environment, CMake applies the toolchain and the launcher reaches the compile rule.

The ccache ceiling also goes from 3G to 6G, since Hermes is far larger than anything cached here so far.

Known limit

[RN] [1] Build Hermesc is out of reach. It runs its CMake under env -i with only PATH and SDKROOT, so no environment-based injection can reach it and it keeps costing its 3m40s. Reaching that one would need a change in React Native itself.

What this does not do

The bigger prize is not compiling Hermes at all. React Native supports it — HERMES_ENGINE_TARBALL_PATH selects LOCAL_PREBUILT_TARBALL and the script phases then do not run — but hermes_source_type() checks override_hermes_dir_envvar_defined() first, and that override comes from this repo's Podfile integration rather than from CI. So consuming a prebuilt Hermes is a change to how the library vendors Hermes, affecting consumers and not just this workflow. Deliberately left for a separate PR.

Test plan

  • Test app (iOS) passes (label-gated on Apple 🍎, added)
  • First run seeds the cache — expect no speed-up, and confirm the Hermes compiles now register as cacheable in the ccache stats
  • A second run on the same commit should show the [2] Build Hermes phase drop sharply

Generated by Claude Code

Hermes dominates the iOS build. In run 31691647982 the build step took
18m58s, of which '[RN] [1] Build Hermesc' was 3m40s and '[RN] [2] Build
Hermes' 13m35s — 17m15s, or 91%. It is not hidden concurrency either: no
CompileC task appears during that window, and all 54 of them land in the
final 66 seconds. Hermes serialises the build.

Ccache never saw any of it. C_COMPILER_LAUNCHER is an Xcode build setting,
while Hermes is built by CMake from a script phase, so the two never meet —
ccache logged 549 calls for that whole job, of which only 68 were cacheable.

React Native hard-codes the CMake flags in build-hermes-xcode.sh with no
hook for extra arguments, but it does not sanitise the environment, and
CMake reads CMAKE_TOOLCHAIN_FILE from there (verified locally: the toolchain
is applied and the launcher reaches the compile rule). So point it at a
generated toolchain file that sets CMAKE_C/CXX_COMPILER_LAUNCHER, which
needs no patching of React Native.

'[1] Build Hermesc' is out of reach: it runs its CMake under `env -i` with
only PATH and SDKROOT, so it keeps costing its 3m40s.

Also raises the ccache ceiling to 6G, since Hermes is much larger than
anything cached so far.
@kraenhansen kraenhansen added CI Continuous integration Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) labels Aug 13, 2026 — with Claude
@kraenhansen kraenhansen self-assigned this Aug 13, 2026
The Apple 🍎 label landed after the run for 2d227ef started, so Test app
(iOS) evaluated its label gate against a payload that did not yet have it
and skipped. Workflow triggers on opened/synchronize/reopened, so labelling
alone does not re-run it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) CI Continuous integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants