From 4e8f5875bc59ddfb84c8b0b26071a547b49823a9 Mon Sep 17 00:00:00 2001 From: Ivancing <648262030@qq.com> Date: Mon, 22 Jul 2024 01:40:42 +0800 Subject: [PATCH] fix(cli): add NAPI support in standalone mode (#24642) Currently, importing Node-Addons modules in a standalone binary results in a `missing symbol called` error (https://github.com/denoland/deno/issues/24614). Because the NAPI symbols are not exported in this mode. This PR should fix the issue. --- cli/build.rs | 47 ++++++++++++++----- cli/mainrt.rs | 1 + tests/integration/compile_tests.rs | 63 ++++++++++++++++++++++++++ tests/testdata/compile/napi/main.ts | 7 +++ tests/testdata/compile/napi/module.c | 68 ++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 tests/testdata/compile/napi/main.ts create mode 100644 tests/testdata/compile/napi/module.c diff --git a/cli/build.rs b/cli/build.rs index 4fe6fd1eab..f131bc1dc8 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -404,16 +404,28 @@ fn main() { ); #[cfg(target_os = "windows")] - println!( - "cargo:rustc-link-arg-bin=deno=/DEF:{}", - symbols_path.display() - ); + { + println!( + "cargo:rustc-link-arg-bin=deno=/DEF:{}", + symbols_path.display() + ); + println!( + "cargo:rustc-link-arg-bin=denort=/DEF:{}", + symbols_path.display() + ); + } #[cfg(target_os = "macos")] - println!( - "cargo:rustc-link-arg-bin=deno=-Wl,-exported_symbols_list,{}", - symbols_path.display() - ); + { + println!( + "cargo:rustc-link-arg-bin=deno=-Wl,-exported_symbols_list,{}", + symbols_path.display() + ); + println!( + "cargo:rustc-link-arg-bin=denort=-Wl,-exported_symbols_list,{}", + symbols_path.display() + ); + } #[cfg(target_os = "linux")] { @@ -426,19 +438,30 @@ fn main() { { println!("cargo:warning=Compiling with all symbols exported, this will result in a larger binary. Please use glibc 2.35 or later for an optimised build."); println!("cargo:rustc-link-arg-bin=deno=-rdynamic"); + println!("cargo:rustc-link-arg-bin=denort=-rdynamic"); } else { println!( "cargo:rustc-link-arg-bin=deno=-Wl,--export-dynamic-symbol-list={}", symbols_path.display() ); + println!( + "cargo:rustc-link-arg-bin=denort=-Wl,--export-dynamic-symbol-list={}", + symbols_path.display() + ); } } #[cfg(target_os = "android")] - println!( - "cargo:rustc-link-arg-bin=deno=-Wl,--export-dynamic-symbol-list={}", - symbols_path.display() - ); + { + println!( + "cargo:rustc-link-arg-bin=deno=-Wl,--export-dynamic-symbol-list={}", + symbols_path.display() + ); + println!( + "cargo:rustc-link-arg-bin=denort=-Wl,--export-dynamic-symbol-list={}", + symbols_path.display() + ); + } // To debug snapshot issues uncomment: // op_fetch_asset::trace_serializer(); diff --git a/cli/mainrt.rs b/cli/mainrt.rs index aafbf79320..ef163dd00f 100644 --- a/cli/mainrt.rs +++ b/cli/mainrt.rs @@ -15,6 +15,7 @@ mod errors; mod file_fetcher; mod http_util; mod js; +mod napi; mod node; mod npm; mod resolver; diff --git a/tests/integration/compile_tests.rs b/tests/integration/compile_tests.rs index 17054637e1..cd6e429b3a 100644 --- a/tests/integration/compile_tests.rs +++ b/tests/integration/compile_tests.rs @@ -1283,3 +1283,66 @@ fn standalone_jsr_dynamic_import() { output.assert_exit_code(0); output.assert_matches_text("Hello world\n"); } + +#[test] +fn standalone_require_node_addons() { + #[cfg(not(target_os = "windows"))] + { + let context = TestContextBuilder::for_jsr().build(); + let dir = context.temp_dir(); + let libout = dir.path().join("module.node"); + + let cc = context + .new_command() + .name("cc") + .current_dir(util::testdata_path()); + + #[cfg(not(target_os = "macos"))] + let c_module = cc + .arg("./compile/napi/module.c") + .arg("-shared") + .arg("-o") + .arg(&libout); + + #[cfg(target_os = "macos")] + let c_module = { + cc.arg("./compile/napi/module.c") + .arg("-undefined") + .arg("dynamic_lookup") + .arg("-shared") + .arg("-Wl,-no_fixup_chains") + .arg("-dynamic") + .arg("-o") + .arg(&libout) + }; + let c_module_output = c_module.output().unwrap(); + + assert!(c_module_output.status.success()); + + let exe = if cfg!(windows) { + dir.path().join("main.exe") + } else { + dir.path().join("main") + }; + + context + .new_command() + .env("NPM_CONFIG_REGISTRY", "https://registry.npmjs.org/") + .args_vec([ + "compile", + "--allow-read", + "--allow-ffi", + "--output", + &exe.to_string_lossy(), + "./compile/napi/main.ts", + ]) + .run() + .skip_output_check() + .assert_exit_code(0); + + let output = context.new_command().name(&exe).arg(&libout).run(); + + output.assert_exit_code(0); + output.assert_matches_text("{}\n"); + } +} diff --git a/tests/testdata/compile/napi/main.ts b/tests/testdata/compile/napi/main.ts new file mode 100644 index 0000000000..91e8a05613 --- /dev/null +++ b/tests/testdata/compile/napi/main.ts @@ -0,0 +1,7 @@ +import Module from "node:module"; + +const mod = new Module(""); + +const filepath = Deno.args[0]; + +console.log(mod.require(filepath)); diff --git a/tests/testdata/compile/napi/module.c b/tests/testdata/compile/napi/module.c new file mode 100644 index 0000000000..4548aa37fb --- /dev/null +++ b/tests/testdata/compile/napi/module.c @@ -0,0 +1,68 @@ +typedef struct napi_module { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + void* nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#ifdef _WIN32 +#define NAPI_EXTERN __declspec(dllexport) +#define NAPI_CDECL __cdecl +#else +#define NAPI_EXTERN __attribute__((visibility("default"))) +#define NAPI_CDECL +#endif + +NAPI_EXTERN void NAPI_CDECL +napi_module_register(napi_module* mod); + +#if defined(_MSC_VER) +#if defined(__cplusplus) +#define NAPI_C_CTOR(fn) \ + static void NAPI_CDECL fn(void); \ + namespace { \ + struct fn##_ { \ + fn##_() { fn(); } \ + } fn##_v_; \ + } \ + static void NAPI_CDECL fn(void) +#else // !defined(__cplusplus) +#pragma section(".CRT$XCU", read) +// The NAPI_C_CTOR macro defines a function fn that is called during CRT +// initialization. +// C does not support dynamic initialization of static variables and this code +// simulates C++ behavior. Exporting the function pointer prevents it from being +// optimized. See for details: +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170 +#define NAPI_C_CTOR(fn) \ + static void NAPI_CDECL fn(void); \ + __declspec(dllexport, allocate(".CRT$XCU")) void(NAPI_CDECL * fn##_)(void) = \ + fn; \ + static void NAPI_CDECL fn(void) +#endif // defined(__cplusplus) +#else +#define NAPI_C_CTOR(fn) \ + static void fn(void) __attribute__((constructor)); \ + static void fn(void) +#endif + +#define NAPI_MODULE_TEST(modname, regfunc) \ + static napi_module _module = { \ + 1, \ + 0, \ + __FILE__, \ + regfunc, \ + #modname, \ + 0, \ + {0}, \ + }; \ + NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \ + +void* init(void* env __attribute__((unused)), void* exports) { + return exports; +} + +NAPI_MODULE_TEST(TEST_NAPI_MODULE_NAME, init)