diff --git a/.changeset/prebuilt-hermes-visionos-env.md b/.changeset/prebuilt-hermes-visionos-env.md new file mode 100644 index 00000000..0b6876e4 --- /dev/null +++ b/.changeset/prebuilt-hermes-visionos-env.md @@ -0,0 +1,13 @@ +--- +"react-native-node-api": patch +--- + +Fix `prebuilt-hermes` failing to configure the host Hermes compiler with "Host +compiler appears to require libatomic, but cannot find it". It exported all +three deployment targets Hermes' `build-apple-framework.sh` can ask for to every +command it ran, including the host compiler build. `XROS_DEPLOYMENT_TARGET` is +also a clang driver variable, so clang targeted visionOS against the macOS +sysroot, and every API marked unavailable there — `pthread_mutexattr_init`, the +`fd_set` helpers reached through `unistd.h` — failed to compile. Each platform +build now gets only the deployment target it needs, and the host compiler build +gets none. diff --git a/.github/workflows/hermes-prebuilt.yml b/.github/workflows/hermes-prebuilt.yml index 9c9a1a79..f5fd7c27 100644 --- a/.github/workflows/hermes-prebuilt.yml +++ b/.github/workflows/hermes-prebuilt.yml @@ -67,6 +67,37 @@ jobs: run: | path=$(pnpm exec react-native-node-api prebuilt-hermes --no-download) echo "path=$path" >> "$GITHUB_OUTPUT" + # CMake reports a failed feature check as a bare "not found", with the + # compiler's actual complaint only in its configure log. Surface that log + # here so a failure is diagnosable without another round trip. + - name: Dump CMake configure log + if: failure() + run: | + log=$(find "$PWD" -name CMakeConfigureLog.yaml -path '*build_host_hermesc*' | head -1) + if [ -z "$log" ]; then + echo "No CMakeConfigureLog.yaml found" + find "$PWD" -name 'CMakeError.log' -path '*build_host_hermesc*' -exec cat {} \; + exit 0 + fi + echo "::group::Environment seen by CMake" + env | sort + xcode-select --print-path + xcrun --show-sdk-path || true + echo "::endgroup::" + echo "::group::unistd.h check" + grep -n -B 5 -A 45 'unistd\.h' "$log" | head -150 + echo "::endgroup::" + echo "::group::atomics check" + grep -n -B 5 -A 60 'HAVE_CXX_ATOMICS_WITHOUT_LIB' "$log" | head -180 + echo "::endgroup::" + cp "$log" "$RUNNER_TEMP/CMakeConfigureLog.yaml" + - name: Upload CMake configure log + if: failure() + uses: actions/upload-artifact@v7 + with: + name: hermesc-cmake-configure-log + path: ${{ runner.temp }}/CMakeConfigureLog.yaml + if-no-files-found: ignore # --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/packages/host/src/node/cli/hermes-prebuilt.ts b/packages/host/src/node/cli/hermes-prebuilt.ts index 3470cd09..574e24cf 100644 --- a/packages/host/src/node/cli/hermes-prebuilt.ts +++ b/packages/host/src/node/cli/hermes-prebuilt.ts @@ -27,13 +27,25 @@ const RELEASES_URL = export const DEFAULT_PLATFORMS = ["iphoneos", "iphonesimulator"]; -// Passed to Hermes' build-apple-framework.sh, which errors out rather than -// assume one. These match React Native's own podspec declarations. -const DEPLOYMENT_TARGETS = { - IOS_DEPLOYMENT_TARGET: "15.1", - MAC_DEPLOYMENT_TARGET: "10.15", - XROS_DEPLOYMENT_TARGET: "1.0", -}; +/** + * The deployment target build-apple-framework.sh reads for a platform — it + * errors out rather than assume one. These match React Native's own podspec + * declarations. + * + * Only the variable that platform needs is passed. XROS_DEPLOYMENT_TARGET is + * also a clang driver variable, so leaking it into a build that isn't for + * visionOS makes clang target visionOS against whatever sysroot it was given — + * and every API marked unavailable there then fails to compile. + */ +function getDeploymentTarget(platform: string): Record { + if (platform === "macosx") { + return { MAC_DEPLOYMENT_TARGET: "10.15" }; + } else if (platform === "xros" || platform === "xrsimulator") { + return { XROS_DEPLOYMENT_TARGET: "1.0" }; + } else { + return { IOS_DEPLOYMENT_TARGET: "15.1" }; + } +} export const BUILD_TYPES = ["debug", "release"] as const; export type BuildType = (typeof BUILD_TYPES)[number]; @@ -162,15 +174,19 @@ async function buildArchive({ // vendored copy: the framework and the app linking it share jsi::Runtime. const jsiPath = path.join(reactNativePath, "ReactCommon", "jsi"); - const run = (command: string, args: string[]) => + const run = ( + command: string, + args: string[], + extraEnv: Record = {}, + ) => spawn(command, args, { cwd: hermesPath, outputMode: "inherit", // Keeps the build log off stdout, which callers parse for the final path. stdout: process.stderr, env: { - ...DEPLOYMENT_TARGETS, ...process.env, + ...extraEnv, JSI_PATH: jsiPath, BUILD_TYPE: buildType === "debug" ? "Debug" : "Release", HERMES_OVERRIDE_HERMESC_PATH: importHostCompilersPath, @@ -202,7 +218,11 @@ async function buildArchive({ } for (const platform of platforms) { - await run("./utils/build-apple-framework.sh", [platform]); + await run( + "./utils/build-apple-framework.sh", + [platform], + getDeploymentTarget(platform), + ); } const frameworksPath = path.join(