Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/prebuilt-hermes-host-compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"react-native-node-api": patch
---

Fix `prebuilt-hermes` failing to configure the host Hermes compiler. It passed
`CMAKE_OSX_ARCHITECTURES=arm64;x86_64` to build a universal `hermesc`, but a
multi-arch host configure makes llvh's feature try-compiles fail — standard
headers report as missing and the configure dies with "Host compiler appears to
require libatomic, but cannot find it". The host compiler is now configured the
way Hermes and React Native configure it, for the host architecture only.

`hermesc` is consequently native to the Mac that built the archive, so the
archive name now carries the host architecture. An Intel Mac finds no published
archive for its architecture and builds its own, rather than downloading one
whose `hermesc` it cannot execute.
13 changes: 10 additions & 3 deletions .github/workflows/hermes-prebuilt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@ jobs:
- run: pnpm run build
# Resolved from the test app so the archive is built against the React
# Native version this repository actually pins.
# Each command substitution is assigned before it is echoed: inside
# `echo "x=$(cmd)"` the step's exit status is echo's, so a failing cmd
# passes the step with an empty value.
- name: Resolve prebuilt Hermes name
id: hermes
working-directory: apps/test-app
run: |
echo "archive=$(pnpm exec react-native-node-api prebuilt-hermes --print name)" >> "$GITHUB_OUTPUT"
echo "tag=$(pnpm exec react-native-node-api prebuilt-hermes --print tag)" >> "$GITHUB_OUTPUT"
archive=$(pnpm exec react-native-node-api prebuilt-hermes --print name)
tag=$(pnpm exec react-native-node-api prebuilt-hermes --print tag)
echo "archive=$archive" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- name: Cache prebuilt Hermes
uses: actions/cache@v6
with:
Expand All @@ -59,7 +64,9 @@ jobs:
- name: Build prebuilt Hermes
id: build
working-directory: apps/test-app
run: echo "path=$(pnpm exec react-native-node-api prebuilt-hermes --no-download)" >> "$GITHUB_OUTPUT"
run: |
path=$(pnpm exec react-native-node-api prebuilt-hermes --no-download)
echo "path=$path" >> "$GITHUB_OUTPUT"
# --latest=false keeps these out of the "latest release" slot, which
# belongs to the package releases changesets publishes.
- name: Publish as a release asset
Expand Down
4 changes: 3 additions & 1 deletion docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ The archive is looked for in this order, and cached under `~/Library/Caches/reac
2. The [release asset](https://github.com/callstackincubator/react-native-node-api/releases) published for the pinned commit by the `Hermes prebuilt` workflow, unless `--no-download` is passed.
3. A local build from the vendored source, unless `--no-build` is passed. This requires macOS and Xcode, and takes a while — but only once per pinned commit.

Its name covers everything that changes its contents: the pinned Hermes commit, the React Native version whose `ReactCommon/jsi` it is compiled against, the build type and the platforms. That makes it usable as a CI cache key.
Its name covers everything that changes its contents: the pinned Hermes commit, the React Native version whose `ReactCommon/jsi` it is compiled against, the build type, the platforms and the host architecture. That makes it usable as a CI cache key.

The host architecture is part of it because `destroot/bin/hermesc` is a native binary for whichever Mac built the archive. Archives are published from Apple Silicon runners, so an Intel Mac finds none to download and builds its own instead of getting a `hermesc` it cannot execute.

- `[from]` — Path to a file inside the app package. Defaults to the current working directory.
- `--react-native-package <package-name>` — The React Native package to resolve Hermes for. Defaults to `react-native`.
Expand Down
16 changes: 10 additions & 6 deletions packages/host/src/node/cli/hermes-prebuilt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ export function getPrebuiltDirectory() {
* Identifies an archive by everything that changes its contents. The React
* Native version is part of it because Hermes is compiled against that
* package's ReactCommon/jsi: a JSI mismatch between the framework and the app
* linking it is an ABI break.
* linking it is an ABI break. The host architecture is part of it because the
* hermesc in destroot/bin is a native binary for whichever Mac built it, so a
* host of the other architecture has to build its own rather than download one
* it cannot execute.
*/
export function getArchiveName({
reactNativeVersion,
Expand All @@ -89,7 +92,7 @@ export function getArchiveName({
// GitHub rewrites every character outside [A-Za-z0-9._-] in a release asset
// name, so the name has to stay within that set to survive a round-trip.
const platformSuffix = [...platforms].sort().join("-");
return `hermes-${shortSha}-rn${reactNativeVersion}-${buildType}-${platformSuffix}.tar.gz`;
return `hermes-${shortSha}-rn${reactNativeVersion}-${buildType}-${platformSuffix}-${process.arch}.tar.gz`;
}

export function getReleaseTag() {
Expand Down Expand Up @@ -174,9 +177,11 @@ async function buildArchive({
},
});

// Configured here instead of letting build-apple-framework.sh's
// build_host_hermesc do it: that one takes no architectures, and the hermesc
// we ship has to run on both Apple Silicon and Intel Macs.
// Configured here rather than by build-apple-framework.sh's
// build_host_hermesc only so the build type is explicit — the pinned Hermes
// hard-errors without one. Do not add CMAKE_OSX_ARCHITECTURES: a multi-arch
// host configure makes llvh's try-compiles fail, down to "Host compiler
// appears to require libatomic, but cannot find it".
if (!fs.existsSync(importHostCompilersPath)) {
await run("cmake", [
"-S",
Expand All @@ -185,7 +190,6 @@ async function buildArchive({
hermescPath,
`-DJSI_DIR=${jsiPath}`,
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64",
]);
await run("cmake", [
"--build",
Expand Down
Loading