Skip to content

Commit 6317f7c

Browse files
kraenhansenclaude
andcommitted
vendor-hermes: export public hermes_napi_* entry points
The clean Hermes build at the pinned SHA does NOT export hermes_napi_create_env (and the other hermes_napi_* entry points): Hermes compiles every target with a global -fvisibility=hidden, and because those entry points are C++ symbols taking hermes_napi_host*, a function's visibility is clamped by its parameter types' hidden visibility — so they stay out of the framework's dynamic export table even though they carry NAPI_EXTERN. Last session's env creation only linked because the pod's Hermes binary was hand-patched; a from-scratch build fails at the app link with "Undefined symbol: hermes_napi_create_env". vendor-hermes now appends `-fvisibility=default` to the hermesNapi_obj CMake target (idempotently, after cloning), overriding the earlier global flag for that target only. Verified via nm that the entry points now export (napi/node_api runtime symbols unaffected) and the iOS allTests suite is green (14 passing) from a clean pod build — no binary hand-patching. Also ignore **/build-tests/** in ESLint (CMake writes compiler_depend.ts dependency files there that aren't real TypeScript). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ed4e8c6 commit 6317f7c

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default tseslint.config(
1010
globalIgnores([
1111
"**/dist/**",
1212
"**/build/**",
13+
"**/build-tests/**",
1314
"apps/test-app/ios/**",
1415
"apps/macos-test-app/**",
1516
"packages/host/hermes/**",

packages/host/src/node/cli/hermes.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,31 @@ const platformOption = new Option(
2828
"The React Native package to vendor Hermes into",
2929
).default("react-native");
3030

31+
// Hermes builds every target with a global `-fvisibility=hidden`. The public
32+
// `hermes_napi_*` entry points (e.g. `hermes_napi_create_env`) are C++ symbols
33+
// that take a `hermes_napi_host*`, so under that flag a C++ function's
34+
// visibility is clamped by its parameter types' hidden visibility — keeping the
35+
// entry points out of the shared framework's export table even though they
36+
// carry NAPI_EXTERN. We reach `hermes_napi_create_env` from the host module, so
37+
// it must be exported. Appending `-fvisibility=default` to the napi object
38+
// target overrides the earlier global flag for that target only, exporting the
39+
// entry points (and their parameter types) without touching the rest of Hermes.
40+
const HERMES_NAPI_CMAKELISTS = "API/napi/CMakeLists.txt";
41+
const HERMES_NAPI_VISIBILITY_PATCH =
42+
"target_compile_options(hermesNapi_obj PRIVATE -fvisibility=default)";
43+
44+
async function patchHermesNapiVisibility(hermesPath: string) {
45+
const cmakeListsPath = path.join(hermesPath, HERMES_NAPI_CMAKELISTS);
46+
const contents = await fs.promises.readFile(cmakeListsPath, "utf8");
47+
if (contents.includes(HERMES_NAPI_VISIBILITY_PATCH)) {
48+
return;
49+
}
50+
await fs.promises.writeFile(
51+
cmakeListsPath,
52+
`${contents.trimEnd()}\n\n# Patched by react-native-node-api: export the public hermes_napi_*\n# entry points from the shared framework (see vendor-hermes).\n${HERMES_NAPI_VISIBILITY_PATCH}\n`,
53+
);
54+
}
55+
3156
export const command = new Command("vendor-hermes")
3257
.argument("[from]", "Path to a file inside the app package", process.cwd())
3358
.option("--silent", "Don't print anything except the final path", false)
@@ -115,6 +140,9 @@ export const command = new Command("vendor-hermes")
115140
});
116141
}
117142
}
143+
// Applied unconditionally (idempotent) so an existing checkout from
144+
// before this patch also gets fixed.
145+
await patchHermesNapiVisibility(hermesPath);
118146
console.log(hermesPath);
119147
}),
120148
);

0 commit comments

Comments
 (0)