I would like to embed node.js in Rust. I'm not interested in writing a node.js addon with NAPI or controlling node.js extensively from within rust. All I need is the node.js main() start method - the equivalent of doing node myscript.js.
Why? I am building a self-contained single-file binary desktop application in Rust and would like to run node.js scripts with an embeded node.js runtime. Node.js is not guaranteed to be on the end user's computer, and startup time is sensitive so extracting a self-contained zip of node.js from within the binary to the filesystem is undesirable.
I believe I am having problems with statically linking node.js in my rust (binary) project.
I pull down the nodejs source code
git clone https://github.com/nodejs/node
And rename the main method in node_main.cc
sed -i .bak "s/int main(/int node_main(/g" ./src/node_main.cc
Then I build node.js as static libraries
./configure --enable-static
make -j4
I have a c++ wrapper file wrapper.cpp to expose the node_main() method via extern c
wrapper.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);
}
}
At this point I was able to successfully build the c++ wrapper as a binary while statically linking node.js libs and run node.js from c++ successfully. However, from rust...
main.rs:
extern {
fn run_node();
}
fn main() {
println!("hey there this is RUST");
unsafe { run_node(); }
}
build.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");
}
Note the rustc-link-search path is relative above.
When I run cargo build I get the following error:
= 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)
I'm not sure if linker order is important, but I tried some different ordering combinations with no luck. I also tried linking all the lib .a files from node/out/Release but no difference. I also tried combining all the .a files from node/out/Release into one lib .a file but received duplicate symbol errors. I tried different tags of node.js (like v11.15.0) with no difference.
I am doing this on 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
Cargo.toml:
[build-dependencies]
cc = "1.0"
I'm open to better ways of embedding node.js in rust if there are any good ideas.