Merge pull request #7 from Brooooooklyn/std-future

std future
This commit is contained in:
LongYinan 2020-02-18 22:05:17 +08:00 committed by GitHub
commit 11a96c65fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 744 additions and 374 deletions

76
.github/workflows/linux.yaml vendored Normal file
View file

@ -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

76
.github/workflows/macos.yaml vendored Normal file
View file

@ -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

82
.github/workflows/windows.yaml vendored Normal file
View file

@ -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

3
.gitignore vendored
View file

@ -3,4 +3,5 @@ target/
.DS_Store
Cargo.lock
node_modules
src/sys/bindings.rs
napi/src/sys/bindings.rs
*.node

1
.yarnrc Normal file
View file

@ -0,0 +1 @@
registry "https://registry.npmjs.org"

View file

@ -1,14 +1,6 @@
[package]
name = "napi-rs"
version = "0.1.0"
authors = ["Nathan Sobo <nathan@github.com>", "Yinan Long <lynweklm@gmail.com>"]
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"]

View file

@ -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.

View file

@ -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<P>(exe_name: P) -> Option<PathBuf>
where
P: AsRef<Path>,
{
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");
}

13
build/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "napi-build"
version = "0.1.0"
authors = ["LongYinan <lynweklm@gmail.com>"]
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"] }

1
build/build.rs Normal file
View file

@ -0,0 +1 @@
fn main() {}

67
build/src/lib.rs Normal file
View file

@ -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() { }
}
}

21
napi/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "napi-rs"
version = "0.1.0"
authors = ["Nathan Sobo <nathan@github.com>", "Yinan Long <lynweklm@gmail.com>"]
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"

170
napi/build.rs Normal file
View file

@ -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<Path> {
exe_name.into()
}
#[cfg(target_os = "windows")]
fn enhance_exe_name(exe_name: &Path) -> Cow<Path> {
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<P>(exe_name: P) -> Option<PathBuf>
where
P: AsRef<Path>,
{
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")
}

106
napi/src/executor.rs Normal file
View file

@ -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<Box<dyn Future<Output = ()>>>,
context: Context<'a>,
}
impl LibuvExecutor {
pub fn new(event_loop: *mut sys::uv_loop_s) -> Self {
Self { event_loop }
}
pub fn execute<F: 'static + Future<Output = ()>>(&self, future: F) {
let uninit = mem::MaybeUninit::<sys::uv_async_t>::uninit();
let uv_async_t: Box<sys::uv_async_t> = 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);
}

View file

@ -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<Any>; 8] = unsafe { mem::uninitialized() };
for (i, raw_arg) in raw_args.into_iter().enumerate() {
let mut args = unsafe { mem::MaybeUninit::<[Value<Any>; 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<Object> {
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<Buffer> {
pub fn create_buffer(&self, length: u64) -> Value<Buffer> {
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<Buffer> {
pub fn create_buffer_with_data(&self, data_ptr: *const u8, length: u64) -> Value<Buffer> {
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<Vec<u16>> 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,
)

View file

@ -10,7 +10,9 @@ static v8::Local<v8::Value> 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<extras_callback_scope>(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<node::CallbackScope *>(callback_scope);
}
EXTERN_C_END

View file

@ -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;

View file

@ -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"
}
}

15
scripts/napi.js Executable file → Normal file
View file

@ -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

View file

@ -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<T: 'static + Future> {
spawn: Spawn<T>,
notify_handle: Arc<TaskNotifyHandle>,
}
struct TaskNotifyHandle(RwLock<Option<UvAsyncHandle>>);
struct UvAsyncHandle(Box<sys::uv_async_t>);
impl LibuvExecutor {
pub fn new(event_loop: *mut sys::uv_loop_t) -> Self {
Self { event_loop }
}
}
impl<F> Executor<F> for LibuvExecutor
where
F: 'static + Future<Item = (), Error = ()>,
{
fn execute(&self, future: F) -> Result<(), ExecuteError<F>> {
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::<F>),
mem::transmute_copy(&task),
)),
);
if !task.poll_future() {
mem::forget(task)
}
}
Ok(())
}
}
impl<T: 'static + Future> Task<T> {
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<T: 'static + Future>(handle: *mut sys::uv_async_t) {
let mut task: Box<Task<T>> = unsafe { Box::from_raw((*handle).data as *mut Task<T>) };
if !task.poll_future() {
mem::forget(task); // Don't drop task if it isn't complete.
}
}

View file

@ -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<napi_status> 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,
}
}
}

View file

@ -2,9 +2,13 @@
name = "test-module"
version = "0.1.0"
authors = ["LongYinan <lynweklm@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["dylib"]
crate-type = ["cdylib"]
[dependencies]
napi-rs = {path = ".."}
napi-rs = {path = "../napi"}
[build-dependencies]
napi-build = { path = "../build" }

7
test_module/build.rs Normal file
View file

@ -0,0 +1,7 @@
extern crate napi_build;
fn main() {
use napi_build::setup;
setup();
}

View file

@ -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": {

View file

@ -25,41 +25,29 @@ fn test_spawn<'a>(
_this: Value<'a, Any>,
_args: &[Value<'a, Any>],
) -> Result<Option<Value<'a, Any>>> {
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::<i32>();
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::<Vec<i32>>();
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()))
}

View file

@ -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)

View file

@ -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"

View file

@ -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==