Repository: denoland/deno
Pull request: fix(cli): add NAPI support in standalone mode

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. 





Changed files:
filename: cli/build.rs
patch: @@ -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();

filename: cli/mainrt.rs
patch: @@ -15,6 +15,7 @@ mod errors;
 mod file_fetcher;
 mod http_util;
 mod js;
+mod napi;
 mod node;
 mod npm;
 mod resolver;

filename: tests/integration/compile_tests.rs
patch: @@ -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");
+  }
+}

filename: tests/testdata/compile/napi/main.ts
patch: @@ -0,0 +1,7 @@
+import Module from "node:module";
+
+const mod = new Module("");
+
+const filepath = Deno.args[0];
+
+console.log(mod.require(filepath));

filename: tests/testdata/compile/napi/module.c
patch: @@ -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)