Skip to content

Add a prebuilt-hermes command and a workflow that publishes its archive - #440

Merged
kraenhansen merged 2 commits into
nextfrom
claude/hermes-source-build-cache-zk13mc
Aug 13, 2026
Merged

Add a prebuilt-hermes command and a workflow that publishes its archive#440
kraenhansen merged 2 commits into
nextfrom
claude/hermes-source-build-cache-zk13mc

Conversation

@kraenhansen

@kraenhansen kraenhansen commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Building Hermes for Apple platforms is the expensive part of an iOS build — 17m15s of an 18m58s Build test app step, measured in #439 — and it only changes when the pinned commit does.

This lands the machinery for building it once, but nothing consumes it yet: pod install still builds Hermes from source, exactly as before. Splitting it out means the Hermes prebuilt workflow reaches the default branch and becomes dispatchable, so an asset exists before anything depends on it. #TBD flips the Cocoapods integration over.

prebuilt-hermes

A new command that resolves an archive of the pinned Hermes, prebuilt for Apple platforms, and prints its path. The archive holds the destroot layout hermes-engine.podspec expects from a tarball pointed at by HERMES_ENGINE_TARBALL_PATH, which makes hermes_source_type() return LOCAL_PREBUILT_TARBALL and skips the [RN] [1] Build Hermesc / [RN] [2] Build Hermes script phases entirely.

Resolution order: local cache → release asset published for the pinned commit → local build. --no-download / --no-build narrow it, --print name|tag|url reports without resolving.

The per-platform framework builds are delegated to the Hermes checkout's own utils/build-apple-framework.sh — the script that knows how to build that source tree — rather than reimplemented. Everything around it is TypeScript: the host compiler, the universal XCFramework and the archive.

Details worth a look

The archive is keyed on the React Native version, not just the Hermes commit. React Native passes its own ReactCommon/jsi as JSI_DIR; the framework and the app linking it share jsi::Runtime, so a mismatch is an ABI break rather than a compile error. The name is hermes-<sha12>-rn<version>-<buildType>-<platforms>.tar.gz, which also makes it a correct CI cache key. It stays within [A-Za-z0-9._-] because GitHub rewrites anything else in a release asset name.

destroot is archived alongside LICENSE deliberately. cocoapods-downloader flattens an archive whose sole top-level entry is a directory, which would strip the destroot/ prefix that every path in hermes-engine.podspec assumes.

hermesc is built universal and shipped in destroot/bin. react-native-xcode.sh falls back to $HERMES_ENGINE_PATH/destroot/bin/hermesc when HERMES_CLI_PATH is unset, and the podspec deliberately does not point that at the hermes-compiler npm package for local tarballs — the compiler has to emit bytecode this VM can read. It's configured here rather than by build_host_hermesc, which takes no architectures, so the published archive also works on Intel Macs.

macOS is left out of the universal XCFramework. hermes-engine.podspec vendors macOS as a plain framework and every other platform out of the XCFramework, so macosx stays where it is instead of being merged and deleted.

One build type, not per-configuration. HERMES_ENGINE_TARBALL_PATH is resolved once at pod install, so it can't switch between Debug and Release the way React Native's replace_hermes_version.js does for its own prebuilts. The default is debug (Hermes' debugger enabled, CMake Release), the functional superset; --build-type release is there for the smaller one.

The publishing workflow

.github/workflows/hermes-prebuilt.yml builds the archive on macos-latest and uploads it to a release tagged hermes-prebuilt-<sha12>, creating the release if missing. --latest=false keeps these out of the "latest release" slot, which belongs to the package releases changesets publishes. It triggers on workflow_dispatch and on pushes to main/next that touch the pin or the command, and caches by archive name so a re-run doesn't rebuild.

Test plan

  • Check passes — the Apple 🍎 job still exercises vendor-hermes, which was refactored here, so it is the regression check for that
  • After merge: dispatch Hermes prebuilt and confirm it publishes an asset, then that prebuilt-hermes downloads it on a clean cache

Not verified from this worker: the archive has never been built or consumed, since that needs macOS and Xcode. What was verified is the CLI paths (naming, --print, the 404-then-fallback flow), the type-check, lint, prettier, depcheck and publint.

@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
Building Hermes for Apple platforms is the expensive part of an iOS build,
and it only changes when the pinned commit does. Build it once into an
archive in the destroot layout hermes-engine.podspec expects from
HERMES_ENGINE_TARBALL_PATH, and publish it as a release asset keyed by the
pinned commit.

Nothing consumes the archive yet — pod install still builds Hermes from
source. This lands the command and the publishing workflow first, so the
workflow is dispatchable and an asset exists before anything depends on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkNbgdyuKgHaFwT27RahGH
@kraenhansen
kraenhansen force-pushed the claude/hermes-source-build-cache-zk13mc branch from af62873 to f64468e Compare August 13, 2026 14:09
@kraenhansen kraenhansen changed the title Build Hermes once into an archive instead of on every iOS build Add a prebuilt-hermes command and a workflow that publishes its archive Aug 13, 2026
The workflow's pull_request trigger doesn't fire on `labeled`, so the
label-gated jobs need a synchronize event to be evaluated against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkNbgdyuKgHaFwT27RahGH
@kraenhansen
kraenhansen merged commit 166b3bf into next Aug 13, 2026
16 checks passed
@kraenhansen
kraenhansen deleted the claude/hermes-source-build-cache-zk13mc branch August 13, 2026 15:00
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.

1 participant