Skip to content

Build napi as a shared library on Android - #183

Open
matthargett wants to merge 3 commits into
BabylonJS:mainfrom
rebeckerspecialties:napi-shared-android
Open

matthargett wants to merge 3 commits into
BabylonJS:mainfrom
rebeckerspecialties:napi-shared-android

Conversation

@matthargett

@matthargett matthargett commented Jun 5, 2026 •

Copy link
Copy Markdown

Build the napi target as a shared library (libnapi.so) on Android only; every other platform keeps the static library.

Why

  • Native addons can be dlopen'd as standalone .node modules that resolve napi_* through a real DT_NEEDED: the model nodejs/node-api-cts uses, and what the in-process conformance suite in Add N-API compliance tests #116 needs.
  • The engine, the app and individual addons become separately shippable .sos, and addons can load on demand.
  • Standard lib/<abi>/*.so packaging: no runtime code download and no dlopen from a writable directory, so Play and Quest rules are unaffected. Same mechanism as libv8android.so.

Embedder impact (Android only)

  • Every consumer of napi now carries DT_NEEDED libnapi.so. AGP externalNativeBuild and prefab/AAR package it automatically; a hand-rolled .so allow-list must add it, or the app fails at load with library "libnapi.so" not found.
  • About 300 KB more per ABI; napi_* symbols are exported from libnapi.so; exactly one napi instance per process.
  • iOS and Windows are untouched (if(ANDROID)). A shared napi there would be a separate lift: an embedded, signed framework on iOS; napi.dll plus an import library on Windows.
  • If an embedder needs static on Android, the change is a ~6-line diff that can be gated behind a CMake option later.

Verification

libnapi.so is built and packaged (lib/arm64-v8a/libnapi.so), every consumer links it via DT_NEEDED, and on an arm64 emulator (API 29, V8) a standalone .node addon with napi_* as undefined imports dlopens and resolves napi_register_module_v1 against it.

Stack

First of the N-API series: this → #116 (compliance tests) → #189 (N-API v7) → #258 (Worker).

Copilot AI review requested due to automatic review settings June 5, 2026 02:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adjusts how the napi library is built so Android produces a shared libnapi.so, enabling separately dlopen’d native addons to resolve napi_* symbols via dynamic linking.

Changes:

  • Build napi as SHARED on Android, otherwise keep existing default library-type behavior.
  • Add rationale in CMake comments about Android/bionic dlopen and symbol resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Core/Node-API/CMakeLists.txt Outdated
Comment thread Core/Node-API/CMakeLists.txt Outdated
@matthargett

Copy link
Copy Markdown
Author

cc @vmoroz @kraenhansen , thanks for generalizing your work into the upstream nodejs CTS!

RaananW

This comment was marked as off-topic.

@RaananW
RaananW dismissed their stale review September 10, 2026 18:23

Should not have been submitted

Link napi as a shared library (libnapi.so) on Android instead of statically into each consumer, so
native addons can be dlopen'd as standalone .node modules and resolve their napi_* imports via a real
DT_NEEDED. The host and every addon then share a single napi instance. Other platforms keep static
napi.
- Don't hard-force SHARED: add a JSR_NAPI_SHARED CMake option (default ON on Android, OFF elsewhere)
  so integrators can keep a static napi via -DJSR_NAPI_SHARED=OFF without patching the project.
- Fix the comment: the non-shared branch keeps add_library(napi ${SOURCES}), which follows the
  project's default library type (BUILD_SHARED_LIBS), not necessarily static.
Hermes does not ship a js_native_api_hermes.cc -- its C napi_* functions live in the hermesNapi static
library. Building napi as a SHARED library therefore produces a libnapi.so that does not carry those
symbols, and everything linking it fails with undefined references (napi_wrap, napi_create_arraybuffer,
napi_create_external, ...). The shared-napi default exists so dlopen'd .node addons can resolve napi_*
at load; that harness is not built for Hermes, so keep napi static there.
matthargett added a commit to rebeckerspecialties/JsRuntimeHost that referenced this pull request Sep 13, 2026
@matthargett

Copy link
Copy Markdown
Author

On "no Android build validates the new shared/static napi configuration": upstream CI on fork PRs is held at the workflow-approval gate, which is why only GitGuardian reports here. The full matrix — including Android_V8, Android_JSC and Android_Hermes — runs on the fork twin, rebeckerspecialties#12.

Both configurations are exercised there: Android_V8 / Android_JSC build with the default JSR_NAPI_SHARED=ON, and Android_Hermes builds with it OFF (Hermes supplies its napi_* from the hermesNapi static library, so the shared default deliberately excludes it — see the CMake comment). Rebased onto current main (f47991d); no conflicts.

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.

3 participants