diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml new file mode 100644 index 00000000..ced5a36c --- /dev/null +++ b/.github/workflows/linux.yaml @@ -0,0 +1,76 @@ +name: Linux + +on: [push, pull_request] + +jobs: + build_and_test: + strategy: + fail-fast: false + matrix: + node: ['10', '12', '13'] + version: + - stable + - nightly + + name: ${{ matrix.version }} - x86_64-unknown-linux-gnu - node@${{ matrix.node }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: Install ${{ matrix.version }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: check build + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --bins --examples --tests -vvv + + - name: tests + uses: actions-rs/cargo@v1 + timeout-minutes: 40 + with: + command: test + args: -p napi-rs --lib -- --nocapture + + - name: test scripts + run: | + cd test_module + cargo build + cp target/debug/libtest_module.so target/debug/libtest_module.node + node tests.js + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache \ No newline at end of file diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml new file mode 100644 index 00000000..c17ad95d --- /dev/null +++ b/.github/workflows/macos.yaml @@ -0,0 +1,76 @@ +name: macOS + +on: [push, pull_request] + +jobs: + build_and_test: + strategy: + fail-fast: false + matrix: + node: ['10', '12', '13'] + version: + - stable + - nightly + + name: ${{ matrix.version }} - x86_64-apple-darwin - node@${{ matrix.node }} + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: Install ${{ matrix.version }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.version }}-x86_64-apple-darwin + profile: minimal + override: true + + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: check build + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --bins --examples --tests -vvv + + - name: tests + uses: actions-rs/cargo@v1 + timeout-minutes: 40 + with: + command: test + args: -p napi-rs --lib -- --nocapture + + - name: test scripts + run: | + cd test_module + cargo build + cp target/debug/libtest_module.dylib target/debug/libtest_module.node + node tests.js + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache \ No newline at end of file diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml new file mode 100644 index 00000000..81bdf883 --- /dev/null +++ b/.github/workflows/windows.yaml @@ -0,0 +1,82 @@ +name: Windows + +on: [push, pull_request] + +jobs: + build_and_test: + strategy: + fail-fast: false + matrix: + node: ['10', '12', '13'] + version: + - stable + - nightly + target: + - x86_64-pc-windows-msvc + + name: ${{ matrix.version }} - ${{ matrix.target }} - node@${{ matrix.node }} + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - name: Install ${{ matrix.version }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.version }}-${{ matrix.target }} + profile: minimal + override: true + - name: Install llvm + run: choco install -y llvm + - name: set environment variables + uses: allenevans/set-env@v1.0.0 + with: + LIBCLANG_PATH: 'C:\Program Files\LLVM\bin' + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: ${{ matrix.version }}-${{ matrix.target }}-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: ${{ matrix.version }}-${{ matrix.target }}-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: actions/cache@v1 + with: + path: target + key: ${{ matrix.version }}-${{ matrix.target }}-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }} + + - name: check build + uses: actions-rs/cargo@v1 + with: + command: check + args: -p napi-rs -vvv + + - name: tests + uses: actions-rs/cargo@v1 + timeout-minutes: 40 + with: + command: test + args: -p napi-rs --lib -- --nocapture + + - name: test scripts + run: | + cd test_module + cargo build + cp target/debug/test_module.dll target/debug/libtest_module.node + node tests.js + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/.gitignore b/.gitignore index 03e486a0..544e0205 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ target/ .DS_Store Cargo.lock node_modules -src/sys/bindings.rs +napi/src/sys/bindings.rs +*.node diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 00000000..08b5d85c --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +registry "https://registry.npmjs.org" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d36667ee..1e3888c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,6 @@ -[package] -name = "napi-rs" -version = "0.1.0" -authors = ["Nathan Sobo ", "Yinan Long "] -license = "MIT" - -[dependencies] -futures = "0.3.1" - -[build-dependencies] -bindgen = "0.52" -cc = "1.0" -glob = "0.3" -semver = "0.9" +[workspace] +members = [ + "./build", + "./napi" +] +exclude = ["./test_module"] diff --git a/README.md b/README.md index dce68c7d..4913980c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,25 @@ -# napi +# napi-rs > This project was initialized from [xray](https://github.com/atom/xray) +# Platform Support + +![](https://github.com/Brooooooklyn/napi-rs/workflows/macOS/badge.svg) +![](https://github.com/Brooooooklyn/napi-rs/workflows/Linux/badge.svg) +![](https://github.com/Brooooooklyn/napi-rs/workflows/Windows/badge.svg) + +## Operating Systems + +| Linux | macOS | Windows x64 MSVC | +| ----- | ----- | ---------------- | +| ✓ | ✓ | ✓ | + +## Python + +| Node10 | Node 12 | Node13 | +| --------- | --------- | --------- | +| ✓ | ✓ | ✓ | + A minimal library for building compiled Node add-ons in Rust. This library depends on N-API and requires Node 8.9 or later. It is still pretty raw and has not been tested in a production setting. diff --git a/build.rs b/build.rs deleted file mode 100644 index 0b0bf3ca..00000000 --- a/build.rs +++ /dev/null @@ -1,87 +0,0 @@ -extern crate bindgen; -extern crate cc; -extern crate glob; -extern crate semver; - -use glob::glob; -use std::env; -use std::path::{Path, PathBuf}; -use std::process::Command; - -fn find_it

(exe_name: P) -> Option -where - P: AsRef, -{ - env::var_os("PATH").and_then(|paths| { - env::split_paths(&paths) - .filter_map(|dir| { - let full_path = dir.join(&exe_name); - if full_path.is_file() { - Some(full_path) - } else { - None - } - }) - .next() - }) -} - -fn main() { - let node_include_path = find_it("node") - .expect("can not find executable node") - .parent() - .unwrap() - .parent() - .unwrap() - .join("include/node"); - let node_version = semver::Version::parse( - String::from_utf8(Command::new("node").arg("-v").output().unwrap().stdout) - .unwrap() - .as_str() - .get(1..) - .unwrap(), - ) - .unwrap(); - - let node_major_version = node_version.major; - - println!("cargo:rerun-if-env-changed=NODE_INCLUDE_PATH"); - for entry in glob("./src/sys/**/*.*").unwrap() { - println!( - "cargo:rerun-if-changed={}", - entry.unwrap().to_str().unwrap() - ); - } - - // Activate the "node8" or "nodestable" feature for compatibility with - // different versions of Node.js/N-API. - println!( - "cargo:rustc-cfg=node{}", - if node_major_version > 8 { - "stable" - } else if node_major_version == 8 { - "8" - } else { - panic!("node version is too low") - } - ); - - bindgen::Builder::default() - .header("src/sys/bindings.h") - .clang_arg(String::from("-I") + node_include_path.to_str().unwrap()) - .rustified_enum("(napi_|uv_).+") - .whitelist_function("(napi_|uv_|extras_).+") - .whitelist_type("(napi_|uv_|extras_).+") - .generate() - .expect("Unable to generate napi bindings") - .write_to_file("src/sys/bindings.rs") - .expect("Unable to write napi bindings"); - - cc::Build::new() - .cpp(true) - .include(&node_include_path) - .file("src/sys/bindings.cc") - .flag("-std=c++0x") - .flag("-Wno-unused-parameter") - .compile("extras"); -} diff --git a/build/Cargo.toml b/build/Cargo.toml new file mode 100644 index 00000000..8eda568c --- /dev/null +++ b/build/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "napi-build" +version = "0.1.0" +authors = ["LongYinan "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cfg-if = "0.1" + +[target.'cfg(windows)'.dependencies] +reqwest = { version = "0.10", features = ["native-tls", "blocking"] } diff --git a/build/build.rs b/build/build.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/build/build.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/build/src/lib.rs b/build/src/lib.rs new file mode 100644 index 00000000..e2561b74 --- /dev/null +++ b/build/src/lib.rs @@ -0,0 +1,67 @@ +extern crate cfg_if; +#[cfg(windows)] +extern crate reqwest; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(windows)] { + use std::env::var; + use std::fs::File; + use std::io::copy; + use std::path::PathBuf; + use std::process::Command; + + pub fn setup() { + let node_full_version = + String::from_utf8(Command::new("node").arg("-v").output().unwrap().stdout).unwrap(); + let node_exec_path = String::from_utf8( + Command::new("node") + .arg("-e") + .arg("console.log(process.execPath)") + .output() + .unwrap() + .stdout, + ) + .unwrap(); + let node_exec_path = node_exec_path.trim_end(); + let mut node_lib_file_dir = PathBuf::from(node_exec_path) + .parent() + .unwrap() + .to_path_buf(); + let node_lib_dir = PathBuf::from(&node_lib_file_dir); + node_lib_file_dir.push(format!("node-{}.lib", node_full_version.trim_end())); + if !node_lib_file_dir.exists() { + let lib_file_download_url = format!( + "https://nodejs.org/dist/{}/win-x64/node.lib", + node_full_version + ); + let mut resp = + reqwest::blocking::get(&lib_file_download_url).expect("Download node.lib file failed"); + let mut node_lib_file = File::create(&node_lib_file_dir).unwrap(); + copy(&mut resp, &mut node_lib_file).expect("Save node.lib file failed"); + } + println!( + "cargo:rustc-link-lib={}", + &node_lib_file_dir.file_stem().unwrap().to_str().unwrap() + ); + println!("cargo:rustc-link-search={}", node_lib_dir.to_str().unwrap()); + // Link `win_delay_load_hook.obj` for windows electron + let node_runtime_env = "npm_config_runtime"; + println!("cargo:rerun-if-env-changed={}", node_runtime_env); + if var(node_runtime_env).map(|s| s == "electron") == Ok(true) { + println!("cargo:rustc-cdylib-link-arg=win_delay_load_hook.obj"); + println!("cargo:rustc-cdylib-link-arg=delayimp.lib"); + println!("cargo:rustc-cdylib-link-arg=/DELAYLOAD:node.exe"); + } + } + } else if #[cfg(target_os = "macos")] { + /// Set up the build environment by setting Cargo configuration variables. + pub fn setup() { + println!("cargo:rustc-cdylib-link-arg=-undefined"); + println!("cargo:rustc-cdylib-link-arg=dynamic_lookup"); + } + } else { + pub fn setup() { } + } +} diff --git a/napi/Cargo.toml b/napi/Cargo.toml new file mode 100644 index 00000000..dbe41327 --- /dev/null +++ b/napi/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "napi-rs" +version = "0.1.0" +authors = ["Nathan Sobo ", "Yinan Long "] +license = "MIT" + +[dependencies] +futures = { version = "0.3", features = ["default", "thread-pool"] } + +[target.'cfg(windows)'.build-dependencies] +flate2 = "1.0" +reqwest = { version = "0.10", features = ["native-tls", "blocking"] } +tar = "0.4" + +[build-dependencies] +bindgen = "0.53.1" +cc = "1.0" +glob = "0.3" +napi-build = { path = "../build" } +regex = "1.3" +semver = "0.9" \ No newline at end of file diff --git a/napi/build.rs b/napi/build.rs new file mode 100644 index 00000000..3b46178d --- /dev/null +++ b/napi/build.rs @@ -0,0 +1,170 @@ +extern crate bindgen; +extern crate cc; +#[cfg(windows)] +extern crate flate2; +extern crate glob; +extern crate napi_build; +#[cfg(windows)] +extern crate reqwest; +extern crate semver; +#[cfg(windows)] +extern crate tar; + +use glob::glob; + +use std::borrow::Cow; +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +// https://stackoverflow.com/questions/37498864/finding-executable-in-path-with-rust + +#[cfg(not(target_os = "windows"))] +fn enhance_exe_name(exe_name: &Path) -> Cow { + exe_name.into() +} + +#[cfg(target_os = "windows")] +fn enhance_exe_name(exe_name: &Path) -> Cow { + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + + let raw_input: Vec<_> = exe_name.as_os_str().encode_wide().collect(); + let raw_extension: Vec<_> = OsStr::new(".exe").encode_wide().collect(); + + if raw_input.ends_with(&raw_extension) { + exe_name.into() + } else { + let mut with_exe = exe_name.as_os_str().to_owned(); + with_exe.push(".exe"); + PathBuf::from(with_exe).into() + } +} + +fn find_it

(exe_name: P) -> Option +where + P: AsRef, +{ + let exe_name = enhance_exe_name(exe_name.as_ref()); + env::var_os("PATH").and_then(|paths| { + env::split_paths(&paths) + .filter_map(|dir| { + let full_path = dir.join(&exe_name); + if full_path.is_file() { + Some(full_path) + } else { + None + } + }) + .next() + }) +} + +fn main() { + napi_build::setup(); + let node_full_version = + String::from_utf8(Command::new("node").arg("-v").output().unwrap().stdout).unwrap(); + let node_version = semver::Version::parse(node_full_version.as_str().get(1..).unwrap()).unwrap(); + + let node_major_version = node_version.major; + + println!("cargo:rerun-if-env-changed=NODE_INCLUDE_PATH"); + for entry in glob("./src/sys/**/*.*").unwrap() { + println!( + "cargo:rerun-if-changed={}", + entry.unwrap().to_str().unwrap() + ); + } + + env::set_var("CARGO_RUSTC_FLAGS", "-Clink-args=-export_dynamic"); + + if node_major_version < 10 { + panic!("node version is too low") + } + + let node_include_path = find_node_include_path(node_full_version.trim_end()); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + + let mut sys_bindigs_path = PathBuf::from("src"); + sys_bindigs_path.push("sys"); + sys_bindigs_path.push("bindings.h"); + + bindgen::Builder::default() + .header(sys_bindigs_path.to_str().unwrap().to_owned()) + .clang_arg(String::from("-I") + node_include_path.to_str().unwrap()) + .rustified_enum("(napi_|uv_).+") + .whitelist_function("(napi_|uv_|extras_).+") + .whitelist_type("(napi_|uv_|extras_).+") + .generate() + .expect("Unable to generate napi bindings") + .write_to_file(out_path.join("bindings.rs")) + .expect("Unable to write napi bindings"); + + let mut bindings_path = PathBuf::from("src"); + bindings_path.push("sys"); + bindings_path.push("bindings.cc"); + + let mut cc_builder = cc::Build::new(); + + cc_builder + .cpp(true) + .include(&node_include_path) + .file(bindings_path); + if !cfg!(windows) { + cc_builder.flag("-Wno-unused-parameter"); + }; + + if cfg!(target_os = "macos") { + cc_builder.flag("-std=c++0x"); + } else if cfg!(linux) || cfg!(target_env = "gnu") { + cc_builder.flag("-std=c++14"); + } + + cc_builder + .cargo_metadata(true) + .out_dir(&out_path) + .compile("napi-bindings"); +} + +#[cfg(target_os = "windows")] +fn find_node_include_path(node_full_version: &str) -> PathBuf { + let mut node_exec_path = PathBuf::from( + find_it("node") + .expect("can not find executable node") + .parent() + .unwrap(), + ); + node_exec_path.push(format!("node-headers-{}.tar.gz", node_full_version)); + let mut header_dist_path = PathBuf::from(&PathBuf::from(&node_exec_path).parent().unwrap()); + let unpack_path = PathBuf::from(&header_dist_path); + header_dist_path.push(format!("node-{}", node_full_version)); + header_dist_path.push("include"); + header_dist_path.push("node"); + if !header_dist_path.exists() { + let header_file_download_url = String::from_utf8( + Command::new("node") + .args(vec!["-e", "console.log(process.release.headersUrl)"]) + .output() + .unwrap() + .stdout, + ) + .unwrap(); + let resp = reqwest::blocking::get(&header_file_download_url).expect("request failed"); + tar::Archive::new(flate2::read::GzDecoder::new(resp)) + .unpack(&unpack_path) + .expect("Unpack headers file failed"); + }; + header_dist_path +} + +#[cfg(not(target_os = "windows"))] +fn find_node_include_path(_node_full_version: &str) -> PathBuf { + let node_exec_path = find_it("node").expect("can not find executable node"); + node_exec_path + .parent() + .unwrap() + .parent() + .unwrap() + .join("include/node") +} diff --git a/napi/src/executor.rs b/napi/src/executor.rs new file mode 100644 index 00000000..f4c63410 --- /dev/null +++ b/napi/src/executor.rs @@ -0,0 +1,106 @@ +use super::sys; +use futures::task::Poll; +use std::future::Future; +use std::mem; +use std::os::raw::c_void; +use std::pin::Pin; +use std::task::{Context, RawWaker, RawWakerVTable, Waker}; + +pub struct LibuvExecutor { + event_loop: *mut sys::uv_loop_s, +} + +#[derive(Clone, Debug)] +struct LibuvWaker(*mut sys::uv_async_t); + +unsafe impl Send for LibuvWaker {} + +unsafe impl Sync for LibuvWaker {} + +const UV_ASYNC_V_TABLE: RawWakerVTable = RawWakerVTable::new( + clone_executor, + wake_uv_async, + wake_uv_async_by_ref, + drop_uv_async, +); + +unsafe fn clone_executor(uv_async_t: *const ()) -> RawWaker { + RawWaker::new(uv_async_t, &UV_ASYNC_V_TABLE) +} + +unsafe fn wake_uv_async(uv_async_t: *const ()) { + let status = sys::uv_async_send(uv_async_t as *mut sys::uv_async_t); + assert!(status == 0, "wake_uv_async failed"); +} + +unsafe fn wake_uv_async_by_ref(uv_async_t: *const ()) { + let status = sys::uv_async_send(uv_async_t as *mut sys::uv_async_t); + assert!(status == 0, "wake_uv_async_by_ref failed"); +} + +unsafe fn drop_uv_async(uv_async_t_ptr: *const ()) { + sys::uv_unref(uv_async_t_ptr as *mut sys::uv_handle_t); +} + +struct Task<'a> { + future: Pin>>, + context: Context<'a>, +} + +impl LibuvExecutor { + pub fn new(event_loop: *mut sys::uv_loop_s) -> Self { + Self { event_loop } + } + + pub fn execute>(&self, future: F) { + let uninit = mem::MaybeUninit::::uninit(); + let uv_async_t: Box = unsafe { Box::new(uninit.assume_init()) }; + let uv_async_t_ref = Box::leak(uv_async_t); + unsafe { + let status = sys::uv_async_init(self.event_loop, uv_async_t_ref, Some(poll_future)); + assert!(status == 0, "Non-zero status returned from uv_async_init"); + }; + unsafe { + let waker = Waker::from_raw(RawWaker::new( + uv_async_t_ref as *mut _ as *const (), + &UV_ASYNC_V_TABLE, + )); + let context = Context::from_waker(&waker); + let task = Box::leak(Box::new(Task { + future: Box::pin(future), + context, + })); + sys::uv_handle_set_data( + uv_async_t_ref as *mut _ as *mut sys::uv_handle_t, + task as *mut _ as *mut c_void, + ); + task.poll_future(); + } + } +} + +impl<'a> Task<'a> { + fn poll_future(&mut self) -> bool { + match self.future.as_mut().poll(&mut self.context) { + Poll::Ready(_) => true, + Poll::Pending => false, + } + } +} + +unsafe extern "C" fn poll_future(handle: *mut sys::uv_async_t) { + let data_ptr = sys::uv_handle_get_data(handle as *mut sys::uv_handle_t) as *mut Task; + let mut task = Box::from_raw(data_ptr); + if !task.as_mut().poll_future() { + Box::leak(task); + } else { + sys::uv_close( + handle as *mut sys::uv_handle_t, + Some(drop_handle_after_close), + ); + }; +} + +unsafe extern "C" fn drop_handle_after_close(handle: *mut sys::uv_handle_t) { + Box::from_raw(handle); +} diff --git a/src/lib.rs b/napi/src/lib.rs similarity index 95% rename from src/lib.rs rename to napi/src/lib.rs index ac7bff72..371fab55 100644 --- a/src/lib.rs +++ b/napi/src/lib.rs @@ -54,7 +54,7 @@ pub struct Function; #[derive(Clone, Copy, Debug)] pub struct Buffer { data: *const u8, - size: usize, + size: u64, } #[derive(Clone, Copy, Debug)] @@ -160,14 +160,15 @@ macro_rules! callback { ) -> sys::napi_value { const MAX_ARGC: usize = 8; let mut argc = MAX_ARGC; - let mut raw_args: [$crate::sys::napi_value; MAX_ARGC] = unsafe { mem::uninitialized() }; + let mut raw_args = + unsafe { mem::MaybeUninit::<[$crate::sys::napi_value; MAX_ARGC]>::uninit().assume_init() }; let mut raw_this = ptr::null_mut(); unsafe { let status = sys::napi_get_cb_info( raw_env, cb_info, - &mut argc, + &mut argc as *mut usize as *mut u64, &mut raw_args[0], &mut raw_this, ptr::null_mut(), @@ -177,8 +178,8 @@ macro_rules! callback { let env = Env::from_raw(raw_env); let this = Value::from_raw(&env, raw_this); - let mut args: [Value; 8] = unsafe { mem::uninitialized() }; - for (i, raw_arg) in raw_args.into_iter().enumerate() { + let mut args = unsafe { mem::MaybeUninit::<[Value; 8]>::uninit().assume_init() }; + for (i, raw_arg) in raw_args.iter().enumerate() { args[i] = Value::from_raw(&env, *raw_arg) } @@ -247,7 +248,12 @@ impl Env { pub fn create_string<'a, 'b>(&'a self, s: &'b str) -> Value<'a, String> { let mut raw_value = ptr::null_mut(); let status = unsafe { - sys::napi_create_string_utf8(self.0, s.as_ptr() as *const c_char, s.len(), &mut raw_value) + sys::napi_create_string_utf8( + self.0, + s.as_ptr() as *const c_char, + s.len() as u64, + &mut raw_value, + ) }; debug_assert!(Status::from(status) == Status::Ok); Value::from_raw_value(self, raw_value, String) @@ -255,8 +261,9 @@ impl Env { pub fn create_string_utf16<'a, 'b>(&'a self, chars: &[u16]) -> Value<'a, String> { let mut raw_value = ptr::null_mut(); - let status = - unsafe { sys::napi_create_string_utf16(self.0, chars.as_ptr(), chars.len(), &mut raw_value) }; + let status = unsafe { + sys::napi_create_string_utf16(self.0, chars.as_ptr(), chars.len() as u64, &mut raw_value) + }; debug_assert!(Status::from(status) == Status::Ok); Value::from_raw_value(self, raw_value, String) } @@ -270,12 +277,13 @@ impl Env { pub fn create_array_with_length(&self, length: usize) -> Value { let mut raw_value = ptr::null_mut(); - let status = unsafe { sys::napi_create_array_with_length(self.0, length, &mut raw_value) }; + let status = + unsafe { sys::napi_create_array_with_length(self.0, length as u64, &mut raw_value) }; debug_assert!(Status::from(status) == Status::Ok); Value::from_raw_value(self, raw_value, Object) } - pub fn create_buffer(&self, length: usize) -> Value { + pub fn create_buffer(&self, length: u64) -> Value { let mut raw_value = ptr::null_mut(); let mut data = ptr::null_mut(); let status = unsafe { sys::napi_create_buffer(self.0, length, &mut data, &mut raw_value) }; @@ -290,7 +298,7 @@ impl Env { ) } - pub fn create_buffer_with_data(&self, data_ptr: *const u8, length: usize) -> Value { + pub fn create_buffer_with_data(&self, data_ptr: *const u8, length: u64) -> Value { let mut raw_value = ptr::null_mut(); let mut data_raw_ptr = data_ptr as *mut c_void; let status = @@ -316,7 +324,7 @@ impl Env { sys::napi_create_function( self.0, name.as_ptr() as *const c_char, - name.len(), + name.len() as u64, Some(callback), callback as *mut c_void, &mut raw_result, @@ -368,10 +376,10 @@ impl Env { sys::napi_define_class( self.0, name.as_ptr() as *const c_char, - name.len(), + name.len() as u64, Some(constructor_cb), ptr::null_mut(), - raw_properties.len(), + raw_properties.len() as u64, raw_properties.as_ptr(), &mut raw_result, ) @@ -559,7 +567,7 @@ impl ValueType for Object { impl ValueType for Buffer { fn from_raw(env: sys::napi_env, raw: sys::napi_value) -> Self { let mut data = ptr::null_mut(); - let mut size: usize = 0; + let mut size: u64 = 0; let status = unsafe { sys::napi_get_buffer_info(env, raw, &mut data, &mut size) }; debug_assert!(Status::from(status) == Status::Ok); Buffer { @@ -664,10 +672,10 @@ impl<'env, T: ValueType> Value<'env, T> { #[inline] fn get_raw_type(env: sys::napi_env, raw_value: sys::napi_value) -> sys::napi_valuetype { unsafe { - let mut value_type: sys::napi_valuetype = mem::uninitialized(); - let status = sys::napi_typeof(env, raw_value, &mut value_type); + let value_type = ptr::null_mut(); + let status = sys::napi_typeof(env, raw_value, value_type); debug_assert!(Status::from(status) == Status::Ok); - value_type + *value_type } } @@ -692,7 +700,7 @@ impl<'env> Deref for Value<'env, String> { type Target = [u8]; fn deref(&self) -> &[u8] { - let mut written_char_count: usize = 0; + let mut written_char_count: u64 = 0; let len = self.len() + 1; let mut result = Vec::with_capacity(len); unsafe { @@ -700,14 +708,14 @@ impl<'env> Deref for Value<'env, String> { self.env.0, self.raw_value, result.as_mut_ptr(), - len, - &mut written_char_count as *mut usize, + len as u64, + &mut written_char_count, ); debug_assert!(Status::from(status) == Status::Ok); let ptr = result.as_ptr(); mem::forget(result); - slice::from_raw_parts(ptr as *const u8, written_char_count) + slice::from_raw_parts(ptr as *const u8, written_char_count as usize) } } } @@ -722,11 +730,11 @@ impl<'env> Into> for Value<'env, String> { self.env.0, self.raw_value, result.as_mut_ptr(), - result.capacity(), + result.capacity() as u64, &mut written_char_count, ); debug_assert!(Status::from(status) == Status::Ok); - result.set_len(written_char_count); + result.set_len(written_char_count as usize); } result @@ -871,7 +879,7 @@ impl<'env> Deref for Value<'env, Buffer> { type Target = [u8]; fn deref(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.value.data, self.value.size) } + unsafe { slice::from_raw_parts(self.value.data, self.value.size as usize) } } } @@ -882,7 +890,7 @@ impl<'env> DerefMut for Value<'env, Buffer> { let status = unsafe { sys::napi_get_buffer_info(self.env.0, self.raw_value, &mut data, &mut size) }; debug_assert!(Status::from(status) == Status::Ok); - unsafe { slice::from_raw_parts_mut(data as *mut _, size) } + unsafe { slice::from_raw_parts_mut(data as *mut _, size as usize) } } } @@ -895,7 +903,7 @@ impl<'env> Value<'env, Function> { let raw_this = this .map(|v| v.into_raw()) .unwrap_or_else(|| self.env.get_undefined().into_raw()); - let mut raw_args: [sys::napi_value; 8] = unsafe { mem::uninitialized() }; + let mut raw_args = unsafe { mem::MaybeUninit::<[sys::napi_value; 8]>::uninit().assume_init() }; for (i, arg) in args.into_iter().enumerate() { raw_args[i] = arg.raw_value; } @@ -905,7 +913,7 @@ impl<'env> Value<'env, Function> { self.env.0, raw_this, self.raw_value, - args.len(), + args.len() as u64, &raw_args[0], &mut return_value, ) diff --git a/src/sys/bindings.cc b/napi/src/sys/bindings.cc similarity index 82% rename from src/sys/bindings.cc rename to napi/src/sys/bindings.cc index f35ae44b..a255321d 100644 --- a/src/sys/bindings.cc +++ b/napi/src/sys/bindings.cc @@ -10,7 +10,9 @@ static v8::Local V8LocalValueFromJsValue(napi_value v) return local; } -void extras_open_callback_scope(napi_async_context napi_async_context, +EXTERN_C_START + +NAPI_EXTERN void extras_open_callback_scope(napi_async_context napi_async_context, napi_value napi_resource_object, extras_callback_scope *result) { @@ -21,7 +23,9 @@ void extras_open_callback_scope(napi_async_context napi_async_context, *result = reinterpret_cast(new node::CallbackScope(isolate, resource_object, *node_async_context)); } -void extras_close_callback_scope(extras_callback_scope callback_scope) +NAPI_EXTERN void extras_close_callback_scope(extras_callback_scope callback_scope) { delete reinterpret_cast(callback_scope); } + +EXTERN_C_END diff --git a/src/sys/bindings.h b/napi/src/sys/bindings.h similarity index 100% rename from src/sys/bindings.h rename to napi/src/sys/bindings.h diff --git a/src/sys/mod.rs b/napi/src/sys/mod.rs similarity index 54% rename from src/sys/mod.rs rename to napi/src/sys/mod.rs index 1d0a0020..b4f58bfe 100644 --- a/src/sys/mod.rs +++ b/napi/src/sys/mod.rs @@ -3,14 +3,7 @@ #![allow(non_snake_case)] #![allow(dead_code)] -include!("bindings.rs"); +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -#[cfg(node8)] -mod node8; -#[cfg(node8)] -pub use self::node8::Status; - -#[cfg(nodestable)] mod stable; -#[cfg(nodestable)] pub use self::stable::Status; diff --git a/src/sys/stable.rs b/napi/src/sys/stable.rs similarity index 100% rename from src/sys/stable.rs rename to napi/src/sys/stable.rs diff --git a/package.json b/package.json index c46cfd9f..51444734 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,6 @@ "bin": { "napi": "scripts/napi.js" }, - "scripts": { - "check": "scripts/napi.js check" - }, "repository": { "type": "git", "url": "git@github.com:Brooooooklyn/napi-rs.git" @@ -35,13 +32,10 @@ "arrowParens": "always" }, "files": [ - "scripts/napi.js", - "src/**", - "build.rs", - "Cargo.toml", - "Cargo.lock" + "scripts/napi.js" ], "devDependencies": { + "@types/node": "^13.7.1", "prettier": "^1.12.1" } } diff --git a/scripts/napi.js b/scripts/napi.js old mode 100755 new mode 100644 index a89d519e..338e9a58 --- a/scripts/napi.js +++ b/scripts/napi.js @@ -6,10 +6,9 @@ const path = require('path') const os = require('os') const parsedNodeVersion = process.versions.node.match(/^(\d+)\.(\d+)\.(\d+)$/) const nodeMajorVersion = parseInt(parsedNodeVersion[1]) -const nodeMinorVersion = parseInt(parsedNodeVersion[2]) -if (nodeMajorVersion < 8 || (nodeMajorVersion === 8 && nodeMinorVersion < 9)) { - console.error('This build script should be run on Node 8.9 or greater') +if (nodeMajorVersion < 10) { + console.error('This build script should be run on Node 10 or greater') process.exit(1) } @@ -19,17 +18,7 @@ const argv = parseArgs(process.argv.slice(2), { const subcommand = argv._[0] || 'build' -const nodeIncludePath = path.join( - process.argv[0], - '..', - '..', - 'include', - 'node', -) - const moduleName = path.basename(process.cwd()).replace(/-/g, '_') -process.env.NODE_INCLUDE_PATH = nodeIncludePath -process.env.NODE_MAJOR_VERSION = nodeMajorVersion > 8 ? 'stable' : 8 const platform = os.platform() let libExt, platformArgs diff --git a/src/executor.rs b/src/executor.rs deleted file mode 100644 index ca7ee1f5..00000000 --- a/src/executor.rs +++ /dev/null @@ -1,125 +0,0 @@ -use super::sys; -use futures::executor::{self, Notify, Spawn}; -use futures::future::{ExecuteError, Executor}; -use futures::{Async, Future}; -use std::mem; -use std::os::raw::c_void; -use std::ptr; -use std::sync::{Arc, RwLock}; - -pub struct LibuvExecutor { - event_loop: *mut sys::uv_loop_t, -} - -struct Task { - spawn: Spawn, - notify_handle: Arc, -} - -struct TaskNotifyHandle(RwLock>); - -struct UvAsyncHandle(Box); - -impl LibuvExecutor { - pub fn new(event_loop: *mut sys::uv_loop_t) -> Self { - Self { event_loop } - } -} - -impl Executor for LibuvExecutor -where - F: 'static + Future, -{ - fn execute(&self, future: F) -> Result<(), ExecuteError> { - let spawn = executor::spawn(future); - - unsafe { - let mut task = Box::new(Task { - spawn, - notify_handle: mem::uninitialized(), - }); - - ptr::write( - &mut task.notify_handle, - Arc::new(TaskNotifyHandle::new( - self.event_loop, - Some(poll_future_on_main_thread::), - mem::transmute_copy(&task), - )), - ); - - if !task.poll_future() { - mem::forget(task) - } - } - - Ok(()) - } -} - -impl Task { - fn poll_future(&mut self) -> bool { - match self.spawn.poll_future_notify(&self.notify_handle, 0) { - Ok(Async::Ready(_)) => { - let handle = self.notify_handle.0.write().unwrap().take().unwrap(); - handle.close(); - true - } - Ok(Async::NotReady) => false, - Err(_) => panic!("Future yielded an error"), - } - } -} - -impl TaskNotifyHandle { - fn new(event_loop: *mut sys::uv_loop_t, callback: sys::uv_async_cb, data: *mut c_void) -> Self { - TaskNotifyHandle(RwLock::new(Some(UvAsyncHandle::new( - event_loop, callback, data, - )))) - } -} - -impl Notify for TaskNotifyHandle { - fn notify(&self, _id: usize) { - if let Some(ref uv_handle) = *self.0.read().unwrap() { - unsafe { - sys::uv_async_send(mem::transmute_copy(&uv_handle.0)); - } - } - } -} - -impl UvAsyncHandle { - fn new(event_loop: *mut sys::uv_loop_t, callback: sys::uv_async_cb, data: *mut c_void) -> Self { - unsafe { - let mut handle = UvAsyncHandle(Box::new(mem::uninitialized())); - let status = sys::uv_async_init(event_loop, mem::transmute_copy(&handle.0), callback); - assert!(status == 0, "Non-zero status returned from uv_async_init"); - handle.0.data = data; - handle - } - } - - fn close(self) { - unsafe { - sys::uv_close(mem::transmute_copy(&self.0), Some(drop_handle_after_close)); - mem::forget(self.0); - } - } -} - -unsafe impl Send for UvAsyncHandle {} -unsafe impl Sync for UvAsyncHandle {} - -extern "C" fn drop_handle_after_close(handle: *mut sys::uv_handle_t) { - unsafe { - Box::from_raw(handle); - } -} - -extern "C" fn poll_future_on_main_thread(handle: *mut sys::uv_async_t) { - let mut task: Box> = unsafe { Box::from_raw((*handle).data as *mut Task) }; - if !task.poll_future() { - mem::forget(task); // Don't drop task if it isn't complete. - } -} diff --git a/src/sys/node8.rs b/src/sys/node8.rs deleted file mode 100644 index a97838ec..00000000 --- a/src/sys/node8.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::napi_status; - -#[derive(Eq, PartialEq, Debug)] -pub enum Status { - Ok, - InvalidArg, - ObjectExpected, - StringExpected, - NameExpected, - FunctionExpected, - NumberExpected, - BooleanExpected, - ArrayExpected, - GenericFailure, - PendingException, - Cancelled, - EscapeCalledTwice, - HandleScopeMismatch, - StringContainsNull, - CallbackScopeMismatch, -} - -impl From for Status { - fn from(code: napi_status) -> Self { - use self::napi_status::*; - use Status::*; - - match code { - napi_ok => Ok, - napi_invalid_arg => InvalidArg, - napi_object_expected => ObjectExpected, - napi_string_expected => StringExpected, - napi_name_expected => NameExpected, - napi_function_expected => FunctionExpected, - napi_number_expected => NumberExpected, - napi_boolean_expected => BooleanExpected, - napi_array_expected => ArrayExpected, - napi_generic_failure => GenericFailure, - napi_pending_exception => PendingException, - napi_cancelled => Cancelled, - napi_escape_called_twice => EscapeCalledTwice, - napi_handle_scope_mismatch => HandleScopeMismatch, - napi_callback_scope_mismatch => CallbackScopeMismatch, - } - } -} diff --git a/test_module/Cargo.toml b/test_module/Cargo.toml index f6fde11c..e248e0b9 100644 --- a/test_module/Cargo.toml +++ b/test_module/Cargo.toml @@ -2,9 +2,13 @@ name = "test-module" version = "0.1.0" authors = ["LongYinan "] +edition = "2018" [lib] -crate-type = ["dylib"] +crate-type = ["cdylib"] [dependencies] -napi-rs = {path = ".."} +napi-rs = {path = "../napi"} + +[build-dependencies] +napi-build = { path = "../build" } diff --git a/test_module/build.rs b/test_module/build.rs new file mode 100644 index 00000000..985e8b9b --- /dev/null +++ b/test_module/build.rs @@ -0,0 +1,7 @@ +extern crate napi_build; + +fn main() { + use napi_build::setup; + + setup(); +} diff --git a/test_module/package.json b/test_module/package.json index 71198174..6cfde178 100644 --- a/test_module/package.json +++ b/test_module/package.json @@ -2,9 +2,9 @@ "name": "test-module", "version": "1.0.0", "scripts": { - "build": "napi build", - "build-release": "napi build --release", - "check": "napi check", + "build": "../scripts/napi.js build", + "build-release": "../scripts/napi.js build --release", + "check": "../scripts/napi.js check", "test": "node ./tests.js" }, "dependencies": { diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs index eb792f66..2f43902d 100644 --- a/test_module/src/lib.rs +++ b/test_module/src/lib.rs @@ -25,41 +25,29 @@ fn test_spawn<'a>( _this: Value<'a, Any>, _args: &[Value<'a, Any>], ) -> Result>> { - use futures::future::Executor; - use futures::{Future, Stream}; - use std::{thread, time}; + use futures::executor::ThreadPool; + use futures::StreamExt; let async_context = env.async_init(None, "test_spawn"); + let pool = ThreadPool::new().expect("Failed to build pool"); let (promise, deferred) = env.create_promise(); - let (tx, rx) = futures::sync::mpsc::unbounded(); - - let future = rx.for_each(|n: usize| { - println!("Received value {:?}", n); - futures::future::ok(()) - }).and_then(move |_| { + let (tx, rx) = futures::channel::mpsc::unbounded::(); + let fut_values = async move { + let fut_tx_result = async move { + (0..100).for_each(|v| { + tx.unbounded_send(v).expect("Failed to send"); + }) + }; + pool.spawn_ok(fut_tx_result); + let fut_values = rx.map(|v| v * 2).collect::>(); + let results = fut_values.await; + println!("Collected result lenght {}", results.len()); async_context.enter(|env| { env.resolve_deferred(deferred, env.get_undefined()); }); - futures::future::ok(()) - }); + }; - env.create_executor().execute(future).unwrap(); - - for _i in 0..10 { - let thread_tx = tx.clone(); - thread::spawn(move || { - let mut n = 0; - loop { - println!("send {:?}", n); - thread_tx.unbounded_send(n).unwrap(); - n += 1; - thread::sleep(time::Duration::from_millis(50)); - if n == 10 { - break; - } - } - }); - } + env.create_executor().execute(fut_values); Ok(Some(promise.try_into().unwrap())) } diff --git a/test_module/tests.js b/test_module/tests.js index 125d7701..c056e927 100644 --- a/test_module/tests.js +++ b/test_module/tests.js @@ -1,4 +1,4 @@ -const testModule = require('./target/debug/test_module') +const testModule = require(`./target/debug/libtest_module.node`) function testSpawn() { console.log('=== Test spawning a future on libuv event loop') @@ -16,4 +16,12 @@ function testThrow() { process.exit(1) } -testSpawn().then(testThrow) +const future = testSpawn() + +// https://github.com/nodejs/node/issues/29355 +setTimeout(() => { + future.then(testThrow).catch((e) => { + console.error(e) + process.exit(1) + }) +}, 10) diff --git a/test_module/yarn.lock b/test_module/yarn.lock index 4044667d..ecd58b0d 100644 --- a/test_module/yarn.lock +++ b/test_module/yarn.lock @@ -4,10 +4,12 @@ minimist@^1.2.0: version "1.2.0" - resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= napi-rs@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/napi-rs/-/napi-rs-0.1.0.tgz#32c8b57045e939d66bbf1a0d5d36dc0cf188d70c" + version "0.1.1" + resolved "https://registry.npmjs.org/napi-rs/-/napi-rs-0.1.1.tgz#00c823423be5f070e4d5a01b143e4e848f4a2512" + integrity sha512-eYHtHpqneoL3coE0kLr5hbRR9Z3hx97qfNdzXneuUWR3eFs8qNaJZRX2dln2gWieJaYrqUWs+rvR4+jdBlse6Q== dependencies: minimist "^1.2.0" diff --git a/yarn.lock b/yarn.lock index ae851acd..61ee184c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,17 @@ # yarn lockfile v1 +"@types/node@^13.7.1": + version "13.7.1" + resolved "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d" + integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA== + minimist@^1.2.0: version "1.2.0" - resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= prettier@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" + version "1.19.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==