diff --git a/.changeset/prebuilt-hermes-host-compiler.md b/.changeset/prebuilt-hermes-host-compiler.md new file mode 100644 index 00000000..c8dd3a99 --- /dev/null +++ b/.changeset/prebuilt-hermes-host-compiler.md @@ -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. diff --git a/.github/workflows/hermes-prebuilt.yml b/.github/workflows/hermes-prebuilt.yml index cf619033..9c9a1a79 100644 --- a/.github/workflows/hermes-prebuilt.yml +++ b/.github/workflows/hermes-prebuilt.yml @@ -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: @@ -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 diff --git a/docs/CLI.md b/docs/CLI.md index 58c8ec35..a8c62549 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -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 ` — The React Native package to resolve Hermes for. Defaults to `react-native`. diff --git a/packages/host/src/node/cli/hermes-prebuilt.ts b/packages/host/src/node/cli/hermes-prebuilt.ts index 125deb16..3470cd09 100644 --- a/packages/host/src/node/cli/hermes-prebuilt.ts +++ b/packages/host/src/node/cli/hermes-prebuilt.ts @@ -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, @@ -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() { @@ -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", @@ -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",