Skip to content

Pick hermesc the same way React Native does - #58

Open
revopushbot wants to merge 3 commits into
mainfrom
fix/hermesc-resolution-order
Open

revopushbot wants to merge 3 commits into
mainfrom
fix/hermesc-resolution-order

Conversation

@revopushbot

Copy link
Copy Markdown
Contributor

Problem

release-react / release-expo must compile with the same hermesc as the store build. Otherwise the update contains bytecode the app's Hermes runtime can't load. The CLI always preferred react-native/sdks/hermesc, which differs from React Native's own lookup:

  • RN 0.82 with hermesV1Enabled=true: the app uses hermes-compiler (Hermes V1, bytecode v98), but the CLI compiled with sdks/hermesc (v96).
  • iOS: the CLI ignored HERMES_CLI_PATH and the CocoaPods hermes-engine hermesc. React Native's own react-native-xcode.sh warns that the CocoaPods Hermes "may legitimately differ" from npm hermes-compiler.
  • Custom hermesCommand: the CLI only read the legacy project.ext.react form (RN < 0.71), not the current react { hermesCommand = ... } block, and checked sdks/hermesc before it.
  • REACT_NATIVE_OVERRIDE_HERMES_DIR / Hermes built from source: ignored.
  • hermes-compiler not hoisted (installed under react-native/node_modules): not found, so the CLI fell through to a path that doesn't exist.

New lookup order

Android, following detectOSAwareHermesCommand in the gradle plugin:

  1. A literal hermesCommand from react { } or project.ext.react, with %OS-BIN% substituted and resolved against the app module. Groovy expressions, such as Expo's computed path, can't be evaluated and are skipped. React Native's default order lands on the same compiler for those.
  2. REACT_NATIVE_OVERRIDE_HERMES_DIR/build/bin/hermesc, or Hermes built from source in ReactAndroid.
  3. If hermesV1Enabled=true: hermes-compiler, then sdks/hermesc. Otherwise sdks/hermesc, then hermes-compiler.

iOS, following react-native-xcode.sh:

  1. HERMES_CLI_PATH.
  2. <Podfile dir>/Pods/hermes-engine/destroot/bin/hermesc.
  3. hermes-compiler, then sdks/hermesc.

After both lists come the existing legacy fallbacks: the hermes-engine package, then hermesvm.

hermes-compiler is now resolved through react-native (require.resolve(..., { paths: [reactNativeDir] })), which is the same approach Expo SDK 55 and react-native-xcode.sh use.

What React Native ships

RN sdks/hermesc hermes-compiler dependency
≤ 0.81 yes (v96) none
0.82 yes (v96) 0.0.0, an empty placeholder; a V1 opt-in overrides it
0.83 removed 0.14.1 (v96)
0.84+ removed 250829098.x, Hermes V1 (v98)

Testing

  • New test/hermes-command.ts builds fake project layouts for each case: literal react { } command, Expo expression skipped, V1 flag, non-hoisted hermes-compiler, REACT_NATIVE_OVERRIDE_HERMES_DIR, iOS HERMES_CLI_PATH, CocoaPods compiler, and iOS without CocoaPods.
  • On a real RN project (react-native/packages/helloworld), both platforms still resolve to its sdks/hermesc.
  • tsc passes, and the release flow tests pass.

Related: #57, which detects bytecode version mismatches against the base release.

🤖 Generated with Claude Code

revopushbot and others added 3 commits October 9, 2026 01:05
The CLI could compile a release with a different hermesc than the store
binary, producing bytecode the app can't load. Mirror React Native's
lookup order:

- Android (gradle-plugin detectOSAwareHermesCommand): literal
  `hermesCommand` from the `react { }` block or legacy project.ext.react,
  REACT_NATIVE_OVERRIDE_HERMES_DIR / hermesc built from source, then
  hermes-compiler when hermesV1Enabled=true, else sdks/hermesc, else
  hermes-compiler.
- iOS (react-native-xcode.sh): HERMES_CLI_PATH, the CocoaPods
  hermes-engine hermesc, then hermes-compiler.
- Resolve hermes-compiler through react-native so non-hoisted installs
  are found.

Previously sdks/hermesc always won, so RN 0.82 apps with Hermes V1 and
iOS apps whose CocoaPods Hermes differs from npm got the wrong compiler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n-order

# Conflicts:
#	script/react-native-utils.ts
- Resolve a relative `react { hermesCommand }` against react.root (the
  project root), as BundleHermesCTask runs hermesc from there; legacy
  project.ext.react stays relative to the app module. Expand $rootDir
  and $projectDir.
- Kotlin DSL build scripts that set hermesCommand are evaluated with the
  Gradle wrapper (reusing the version-detection init script, now
  generalized to print any JSON); on failure, warn and fall back to the
  default hermesc.
- iOS: read HERMES_CLI_PATH from ios/.xcode.env(.local) too (and load
  .xcode.env, which was never read), and without Pods prefer
  sdks/hermesc unless RCT_HERMES_V1_ENABLED=1.
- Reuse file-utils fileExists and simplify the lookup code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant