如何在 Rust 中静态链接 Node.js?

5 c++ static-linking node.js rust

我想将node.js 嵌入Rust 中。我对使用 NAPI 编写 Node.js 插件或从 Rust 内部广泛控制 Node.js不感兴趣。我需要的只是 node.js main()start 方法 - 相当于执行node myscript.js.

为什么?我正在用 Rust 构建一个独立的单文件二进制桌面应用程序,并希望使用嵌入的 node.js 运行时运行 node.js 脚本。Node.js 不保证位于最终用户的计算机上,并且启动时间很敏感,因此不希望将 Node.js 的独立 zip 从二进制文件中提取到文件系统。

我相信我在 rust(二进制)项目中静态链接 node.js 时遇到问题。

我拉下nodejs源代码

git clone https://github.com/nodejs/node
Run Code Online (Sandbox Code Playgroud)

并将main方法重命名为node_main.cc

sed -i .bak "s/int main(/int node_main(/g" ./src/node_main.cc
Run Code Online (Sandbox Code Playgroud)

然后我将 node.js 构建为静态库

./configure --enable-static
make -j4
Run Code Online (Sandbox Code Playgroud)

我有一个 C++ 包装文件来通过以下方式wrapper.cpp公开该方法node_main()extern c

包装器.cpp:

#include <string>
#include <iostream>

using namespace std;

int node_main(int argc, char **argv);

extern "C" {
  void run_node() {
    cout << "hello there! general kenobi..." << endl;
    char *args[] = { (char*)"tester.js", NULL };
    node_main(1, args);
  }
}
Run Code Online (Sandbox Code Playgroud)

此时,我能够成功地将 C++ 包装器构建为二进制文件,同时静态链接 Node.js 库并成功从 C++ 运行 Node.js。然而,由于生锈...

主要.rs:

extern {
  fn run_node();
}

fn main() {
  println!("hey there this is RUST");
  unsafe { run_node(); }
}
Run Code Online (Sandbox Code Playgroud)

构建.rs:

extern crate cc;

fn main() {
  println!("cargo:rustc-link-search=native=../node/out/Release");

  println!("cargo:rustc-link-lib=static=node");
  println!("cargo:rustc-link-lib=static=uv");

  println!("cargo:rustc-link-lib=static=v8_base");
  println!("cargo:rustc-link-lib=static=v8_libbase");
  println!("cargo:rustc-link-lib=static=v8_snapshot");
  println!("cargo:rustc-link-lib=static=v8_libplatform");

  println!("cargo:rustc-link-lib=static=icuucx");
  println!("cargo:rustc-link-lib=static=icui18n");
  println!("cargo:rustc-link-lib=static=icudata");
  println!("cargo:rustc-link-lib=static=icustubdata");

  println!("cargo:rustc-link-lib=static=brotli");
  println!("cargo:rustc-link-lib=static=nghttp2");

  cc::Build::new()
    .cpp(true)
    .file("wrapper.cpp")
    .compile("libwrapper.a");
}
Run Code Online (Sandbox Code Playgroud)

请注意,rustc-link-search上面的路径是相对的。

当我运行时,cargo build出现以下错误:

= note: Undefined symbols for architecture x86_64:
          "node_main(int, char**)", referenced from:
              _run_node in libwrapper.a(wrapper.o)
        ld: symbol(s) not found for architecture x86_64
        clang: error: linker command failed with exit code 1 (use -v to see invocation)
Run Code Online (Sandbox Code Playgroud)

我不确定链接器顺序是否重要,但我尝试了一些不同的排序组合,但没有成功。我也尝试链接所有 lib.a文件,node/out/Release但没有区别。我还尝试将所有.a文​​件合并node/out/Release到一个 lib.a文件中,但收到重复的符号错误。我尝试了不同的node.js标签(例如v11.15.0),没有任何区别。

我在 MacOS 10.14.5 上执行此操作

$ rustc --version
rustc 1.36.0 (a53f9df32 2019-07-03)
$ cargo --version
cargo 1.36.0 (c4fcfb725 2019-05-15)
$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Devel
oper/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Run Code Online (Sandbox Code Playgroud)

货物.toml:

[build-dependencies]
cc = "1.0"
Run Code Online (Sandbox Code Playgroud)

如果有任何好的想法,我愿意接受在 Rust 中嵌入 Node.js 的更好方法。

小智 5

我发现了一些问题,一旦解决,就可以在 Rust 中成功静态链接 Node.js。

node_main.cc不包含在libnode.a输出中

我通过使用nm查找符号发现了这一点。我无法找到node_main(),并且node_main.o在符号列表中也找不到。因此我意识到,里面的任何东西node_main.cc都不会被出口。

修复:在另一个文件(如node.cc. 请注意,这里我们添加了一个全新的函数,该函数调用node::Start()

节点cc

extern "C" int node_main(int argc, char** argv) {
    return node::Start(argc, argv);
}
Run Code Online (Sandbox Code Playgroud)

需要extern "C",因为 C++ 名称符号损坏

再次,使用该nm工具搜索libnode.a文件中的所有符号,我发现node_main()函数符号已被破坏。为了从 Rust 中找到这个符号,它不能被破坏,这是通过以下命令完成的 extern "C"

修复:确保在要暴露给 Rust 的函数前面加上extern "C"

节点cc

extern "C" int node_main()
Run Code Online (Sandbox Code Playgroud)

根据节点版本,缺少一些附加符号

根据node.js Github问题#27431,一些符号存根不会在静态库中获得输出。我正在使用 node.js v13.xx 标签,这是一个问题,因此我必须为这些额外的存根创建库并将它们链接到 rust 构建配置中。

修复:从存根创建静态库并将它们链接到build.rs

ar rcs obj/Release/lib_stub_code_cache.a obj/Release/obj.target/cctest/src/node_code_cache_stub.o
ar rcs obj/Release/lib_stub_snapshot.a obj/Release/obj.target/cctest/src/node_snapshot_stub.o
Run Code Online (Sandbox Code Playgroud)

最后结果

构建 Node.js

git clone https://github.com/nodejs/node
cd node
printf 'extern "C" int node_main(int argc, char** argv) { return node::Start(argc, argv); }' >> src/node.cc
./configure --enable-static
make -j4

# temporary fix: https://github.com/nodejs/node/issues/27431#issuecomment-487288275
REL=obj/Release
STUBS=$REL/obj.target/cctest/src
ar rcs "$REL/lib_stub_code_cache.a $STUBS/node_code_cache_stub.o"
ar rcs "$REL/lib_stub_snapshot.a $STUBS/node_snapshot_stub.o"
Run Code Online (Sandbox Code Playgroud)

包装器.cpp

#include <string>
#include <iostream>
using namespace std;

extern "C" {
  int node_main(int argc, char** argv);

  void run_node() {
    cout << "hello there! general kenobi...\n";
    char *args[] = { (char*)"tester.js", NULL };
    node_main(1, args);
  }
}
Run Code Online (Sandbox Code Playgroud)

构建.rs

extern crate cc;

fn main() {
  cc::Build::new()
    .cpp(true)
    .file("wrapper.cpp")
    .compile("libwrapper.a");

  println!("cargo:rustc-link-search=native=../node/out/Release");

  println!("cargo:rustc-link-lib=static=node");
  println!("cargo:rustc-link-lib=static=uv");

  // temporary fix - https://github.com/nodejs/node/issues/27431#issuecomment-487288275
  println!("cargo:rustc-link-lib=static=_stub_code_cache");
  println!("cargo:rustc-link-lib=static=_stub_snapshot");
  // end temporary fix

  println!("cargo:rustc-link-lib=static=v8_base_without_compiler");
  println!("cargo:rustc-link-lib=static=v8_compiler");
  println!("cargo:rustc-link-lib=static=v8_initializers");
  println!("cargo:rustc-link-lib=static=v8_libbase");
  println!("cargo:rustc-link-lib=static=v8_libplatform");
  println!("cargo:rustc-link-lib=static=v8_libsampler");
  println!("cargo:rustc-link-lib=static=v8_snapshot");

  println!("cargo:rustc-link-lib=static=icuucx");
  println!("cargo:rustc-link-lib=static=icui18n");
  println!("cargo:rustc-link-lib=static=icudata");
  println!("cargo:rustc-link-lib=static=icustubdata");

  println!("cargo:rustc-link-lib=static=zlib");
  println!("cargo:rustc-link-lib=static=brotli");
  println!("cargo:rustc-link-lib=static=cares");
  println!("cargo:rustc-link-lib=static=histogram");
  println!("cargo:rustc-link-lib=static=http_parser");
  println!("cargo:rustc-link-lib=static=llhttp");
  println!("cargo:rustc-link-lib=static=nghttp2");
  println!("cargo:rustc-link-lib=static=openssl");
}

Run Code Online (Sandbox Code Playgroud)

主程序.rs

extern {
  fn run_node();
}

fn main() {
  println!("a surprise to be sure, but a welcome one");
  unsafe { run_node(); }
}
Run Code Online (Sandbox Code Playgroud)