From 97680cd2f78b3cff3e1edcb42243ffc87ce64cb7 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Thu, 4 Jun 2026 19:15:01 -0700 Subject: [PATCH 1/3] Build napi as a shared library on Android 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. --- Core/Node-API/CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Core/Node-API/CMakeLists.txt b/Core/Node-API/CMakeLists.txt index 5f495695..281593f7 100644 --- a/Core/Node-API/CMakeLists.txt +++ b/Core/Node-API/CMakeLists.txt @@ -261,7 +261,15 @@ Make sure Hermes was fetched at the top-level CMakeLists.txt and NAPI_JAVASCRIPT message(STATUS "Selected ${NAPI_JAVASCRIPT_ENGINE}") endif() -add_library(napi ${SOURCES}) +# On Android, build napi as a shared library (libnapi.so) so that native addons can be dlopen'd as +# standalone .node modules and resolve their napi_* imports via a real DT_NEEDED -- bionic will not +# surface a statically-linked host's napi to a dlopen'd module. The host and every addon then share a +# single napi instance (one libnapi.so). Elsewhere napi remains a static library. +if(ANDROID) + add_library(napi SHARED ${SOURCES}) +else() + add_library(napi ${SOURCES}) +endif() target_include_directories(napi ${INCLUDE_DIRECTORIES}) target_link_libraries(napi ${LINK_LIBRARIES}) From dd9365b7b83c9c96d9872fda1e51cf029b362d99 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Thu, 4 Jun 2026 19:33:45 -0700 Subject: [PATCH 2/3] Gate the shared napi behind a JSR_NAPI_SHARED option (review feedback) - 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. --- Core/Node-API/CMakeLists.txt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Core/Node-API/CMakeLists.txt b/Core/Node-API/CMakeLists.txt index 281593f7..8c91328c 100644 --- a/Core/Node-API/CMakeLists.txt +++ b/Core/Node-API/CMakeLists.txt @@ -261,11 +261,20 @@ Make sure Hermes was fetched at the top-level CMakeLists.txt and NAPI_JAVASCRIPT message(STATUS "Selected ${NAPI_JAVASCRIPT_ENGINE}") endif() -# On Android, build napi as a shared library (libnapi.so) so that native addons can be dlopen'd as -# standalone .node modules and resolve their napi_* imports via a real DT_NEEDED -- bionic will not -# surface a statically-linked host's napi to a dlopen'd module. The host and every addon then share a -# single napi instance (one libnapi.so). Elsewhere napi remains a static library. +# On Android, native addons are dlopen'd as standalone .node modules and resolve their napi_* imports +# from a shared napi at load time -- bionic will not surface a statically-linked host's napi to a +# dlopen'd module, so the host and every addon must share a single libnapi.so. Default napi to a +# shared library on Android so that model works out of the box; an integrator who wants a static napi +# (e.g. for size/packaging) can override with -DJSR_NAPI_SHARED=OFF. The option defaults OFF on other +# platforms, where napi keeps following the project's default library type (i.e. honors +# BUILD_SHARED_LIBS). +set(JSR_NAPI_SHARED_DEFAULT OFF) if(ANDROID) + set(JSR_NAPI_SHARED_DEFAULT ON) +endif() +option(JSR_NAPI_SHARED "Build napi as a shared library (libnapi.so)" ${JSR_NAPI_SHARED_DEFAULT}) + +if(JSR_NAPI_SHARED) add_library(napi SHARED ${SOURCES}) else() add_library(napi ${SOURCES}) From 6d0612725bf531e8259cd959cdff46a78517b1cb Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Mon, 31 Aug 2026 20:49:50 -0700 Subject: [PATCH 3/3] Don't default napi to a shared library for the Hermes engine on Android 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. --- Core/Node-API/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Node-API/CMakeLists.txt b/Core/Node-API/CMakeLists.txt index 8c91328c..6be37f38 100644 --- a/Core/Node-API/CMakeLists.txt +++ b/Core/Node-API/CMakeLists.txt @@ -269,7 +269,7 @@ endif() # platforms, where napi keeps following the project's default library type (i.e. honors # BUILD_SHARED_LIBS). set(JSR_NAPI_SHARED_DEFAULT OFF) -if(ANDROID) +if(ANDROID AND NOT NAPI_JAVASCRIPT_ENGINE STREQUAL "Hermes") set(JSR_NAPI_SHARED_DEFAULT ON) endif() option(JSR_NAPI_SHARED "Build napi as a shared library (libnapi.so)" ${JSR_NAPI_SHARED_DEFAULT})