commit
b1a6a56fcd
8 changed files with 221 additions and 77 deletions
|
@ -11,3 +11,6 @@ version = "0.2.1"
|
|||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
ureq = { version = "1.5.0", default-features = false, features = [ "native-tls" ] }
|
||||
|
|
|
@ -1,80 +1,20 @@
|
|||
extern crate cfg_if;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
mod windows;
|
||||
pub use windows::setup;
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
mod macos;
|
||||
pub use macos::setup;
|
||||
} else {
|
||||
pub fn setup() {
|
||||
setup_napi_feature();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
use std::fs::{create_dir, metadata, write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn download_node_lib() -> Vec<u8> {
|
||||
let script = format!("
|
||||
require('https').get('https://nodejs.org/dist/' + process.version + '/win-x64/node.lib', (res) => {{
|
||||
res.pipe(process.stdout)
|
||||
}})");
|
||||
|
||||
Command::new("node")
|
||||
.arg("-e")
|
||||
.arg(script)
|
||||
.output()
|
||||
.expect("Download node.lib failed")
|
||||
.stdout
|
||||
}
|
||||
|
||||
pub fn setup() {
|
||||
let node_full_version =
|
||||
String::from_utf8(Command::new("node").arg("-v").output().unwrap().stdout).unwrap();
|
||||
let trim_node_full_version = node_full_version.trim_end();
|
||||
let mut node_lib_file_dir =
|
||||
PathBuf::from(String::from_utf8(Command::new("node").arg("-e").arg("console.log(require('os').homedir())").output().unwrap().stdout).unwrap().trim_end().to_owned());
|
||||
|
||||
node_lib_file_dir.push(".napi-rs");
|
||||
|
||||
match create_dir(&node_lib_file_dir) {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
||||
panic!("create {} folder failed: {}", node_lib_file_dir.to_str().unwrap(), err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let link_search_dir = node_lib_file_dir.clone();
|
||||
|
||||
node_lib_file_dir.push(format!("node-{}.lib", trim_node_full_version));
|
||||
|
||||
if let Err(_) = metadata(&node_lib_file_dir) {
|
||||
let node_lib = download_node_lib();
|
||||
write(&node_lib_file_dir, &node_lib).expect(&format!("Could not save file to {}", node_lib_file_dir.to_str().unwrap()));
|
||||
}
|
||||
println!(
|
||||
"cargo:rustc-link-lib={}",
|
||||
&node_lib_file_dir.file_stem().unwrap().to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-search=native={}", link_search_dir.to_str().unwrap());
|
||||
// Link `win_delay_load_hook.obj` for windows electron
|
||||
println!("cargo:rustc-cdylib-link-arg=delayimp.lib");
|
||||
println!("cargo:rustc-cdylib-link-arg=/DELAYLOAD:node.exe");
|
||||
setup_napi_feature();
|
||||
}
|
||||
} 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=-Wl");
|
||||
println!("cargo:rustc-cdylib-link-arg=-undefined");
|
||||
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
|
||||
setup_napi_feature();
|
||||
}
|
||||
} else {
|
||||
pub fn setup() {
|
||||
setup_napi_feature();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_napi_feature() {
|
||||
pub fn setup_napi_feature() {
|
||||
let napi_version = String::from_utf8(
|
||||
Command::new("node")
|
||||
.args(&["-e", "console.log(process.versions.napi)"])
|
||||
|
|
8
build/src/macos.rs
Normal file
8
build/src/macos.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use crate::*;
|
||||
|
||||
pub fn setup() {
|
||||
println!("cargo:rustc-cdylib-link-arg=-Wl");
|
||||
println!("cargo:rustc-cdylib-link-arg=-undefined");
|
||||
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
|
||||
setup_napi_feature();
|
||||
}
|
111
build/src/windows.rs
Normal file
111
build/src/windows.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use crate::*;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs::{metadata, write};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_node_version() -> std::io::Result<String> {
|
||||
let output = Command::new("node").arg("-v").output()?;
|
||||
let stdout_str = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// version should not have a leading "v" or trailing whitespace
|
||||
Ok(stdout_str.trim().trim_start_matches('v').to_string())
|
||||
}
|
||||
|
||||
fn download_node_lib(dist_url: &str, version: &str, arch: &str) -> Vec<u8> {
|
||||
// Assume windows since we know we are building on windows.
|
||||
let url = format!(
|
||||
"{dist_url}/v{version}/win-{arch}/node.lib",
|
||||
dist_url = dist_url,
|
||||
version = version,
|
||||
arch = arch
|
||||
);
|
||||
|
||||
let response = ureq::get(&url).call();
|
||||
if let Some(error) = response.synthetic_error() {
|
||||
panic!("Failed to download node.lib: {:#?}", error);
|
||||
}
|
||||
|
||||
let mut reader = response.into_reader();
|
||||
let mut bytes = vec![];
|
||||
reader.read_to_end(&mut bytes).unwrap();
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn setup() {
|
||||
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is not set");
|
||||
|
||||
// Assume nodejs if not specified.
|
||||
let dist_url =
|
||||
std::env::var("NPM_CONFIG_DISTURL").unwrap_or_else(|_| "https://nodejs.org/dist".to_string());
|
||||
|
||||
// Try to get local nodejs version if not specified.
|
||||
let node_version = std::env::var("NPM_CONFIG_TARGET")
|
||||
.or_else(|_| get_node_version())
|
||||
.expect("Failed to determine nodejs version");
|
||||
|
||||
// NPM also gives us an arch var, but let's trust cargo more.
|
||||
// We translate from cargo's arch env format into npm/gyps's.
|
||||
// See https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch for rust env values.
|
||||
// Nodejs appears to follow `process.arch`.
|
||||
// See https://nodejs.org/docs/latest/api/process.html#process_process_arch for npm env values.
|
||||
let arch = std::env::var("CARGO_CFG_TARGET_ARCH")
|
||||
.map(|arch| match arch.as_str() {
|
||||
"x86" => "x86", // TODO: x86 appears to also be called ia32 in npm_config_arch sometimes. What is the right value?
|
||||
"x86_64" => "x64",
|
||||
"mips" => "mips",
|
||||
"powerpc" => "ppc",
|
||||
"powerpc64" => "ppc64",
|
||||
"arm" => "arm",
|
||||
"aarch64" => "arm64",
|
||||
arch => panic!("Unknown Architecture: {}", arch),
|
||||
})
|
||||
.expect("Failed to determine target arch");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=NPM_CONFIG_DISTURL");
|
||||
println!("cargo:rerun-if-env-changed=NPM_CONFIG_TARGET");
|
||||
|
||||
let mut node_lib_file_path = PathBuf::from(out_dir);
|
||||
let link_search_dir = node_lib_file_path.clone();
|
||||
|
||||
// Hash the dist_url and store it in the node lib file name.
|
||||
let dist_url_hash = {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
dist_url.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
|
||||
// Encode version, arch, and dist_url to detect and reaquire node.lib when these 3 change.
|
||||
let node_lib_file_name = format!(
|
||||
"node-{version}-{arch}-{dist_url_hash}.lib",
|
||||
version = node_version,
|
||||
arch = arch,
|
||||
dist_url_hash = dist_url_hash
|
||||
);
|
||||
node_lib_file_path.push(&node_lib_file_name);
|
||||
|
||||
// If file does not exist, download it.
|
||||
if metadata(&node_lib_file_path).is_err() {
|
||||
let node_lib = download_node_lib(&dist_url, &node_version, &arch);
|
||||
|
||||
write(&node_lib_file_path, &node_lib).expect(&format!(
|
||||
"Could not save file to {}",
|
||||
node_lib_file_path.to_str().unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-lib={}",
|
||||
node_lib_file_path.file_stem().unwrap().to_str().unwrap()
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
link_search_dir.display()
|
||||
);
|
||||
println!("cargo:rustc-cdylib-link-arg=delayimp.lib");
|
||||
println!("cargo:rustc-cdylib-link-arg=/DELAYLOAD:node.exe");
|
||||
|
||||
setup_napi_feature();
|
||||
}
|
|
@ -18,6 +18,9 @@ tokio_rt = ["futures", "tokio", "once_cell"]
|
|||
[dependencies]
|
||||
napi-sys = {version = "0.4", path = "../sys"}
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.9"
|
||||
|
||||
[dependencies.encoding_rs]
|
||||
optional = true
|
||||
version = "0.8"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
napi_build::setup_napi_feature();
|
||||
}
|
||||
|
|
|
@ -110,6 +110,8 @@ mod tokio_rt;
|
|||
#[cfg(all(feature = "libuv", napi4))]
|
||||
mod uv;
|
||||
mod version;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod win_delay_load_hook;
|
||||
|
||||
pub use napi_sys as sys;
|
||||
|
||||
|
|
79
napi/src/win_delay_load_hook.rs
Normal file
79
napi/src/win_delay_load_hook.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
//! The following directly was copied from [neon][].
|
||||
//!
|
||||
//! Rust port of [win_delay_load_hook.cc][].
|
||||
//!
|
||||
//! When the addon tries to load the "node.exe" DLL module, this module gives it the pointer to the
|
||||
//! .exe we are running in instead. Typically, that will be the same value. But if the node executable
|
||||
//! was renamed, you would not otherwise get the correct DLL.
|
||||
//!
|
||||
//! [neon]: https://github.com/neon-bindings/neon/blob/5ffa2d282177b63094c46e92b20b8e850d122e65/src/win_delay_load_hook.rs
|
||||
//! [win_delay_load_hook.cc]: https://github.com/nodejs/node-gyp/blob/e18a61afc1669d4897e6c5c8a6694f4995a0f4d6/src/win_delay_load_hook.cc
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::ptr::null_mut;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, FARPROC, HMODULE, LPVOID};
|
||||
use winapi::shared::ntdef::LPCSTR;
|
||||
use winapi::um::libloaderapi::GetModuleHandleA;
|
||||
|
||||
// Structures hand-copied from
|
||||
// https://docs.microsoft.com/en-us/cpp/build/reference/structure-and-constant-definitions
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
struct DelayLoadProc {
|
||||
fImportByName: BOOL,
|
||||
// Technically this is `union{LPCSTR; DWORD;}` but we don't access it anyways.
|
||||
szProcName: LPCSTR,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
struct DelayLoadInfo {
|
||||
/// size of structure
|
||||
cb: DWORD,
|
||||
/// raw form of data (everything is there)
|
||||
/// Officially a pointer to ImgDelayDescr but we don't access it.
|
||||
pidd: LPVOID,
|
||||
/// points to address of function to load
|
||||
ppfn: *mut FARPROC,
|
||||
/// name of dll
|
||||
szDll: LPCSTR,
|
||||
/// name or ordinal of procedure
|
||||
dlp: DelayLoadProc,
|
||||
/// the hInstance of the library we have loaded
|
||||
hmodCur: HMODULE,
|
||||
/// the actual function that will be called
|
||||
pfnCur: FARPROC,
|
||||
/// error received (if an error notification)
|
||||
dwLastError: DWORD,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
type PfnDliHook = unsafe extern "C" fn(dliNotify: usize, pdli: *const DelayLoadInfo) -> FARPROC;
|
||||
|
||||
const HOST_BINARIES: &[&[u8]] = &[b"node.exe", b"electron.exe"];
|
||||
|
||||
unsafe extern "C" fn load_exe_hook(event: usize, info: *const DelayLoadInfo) -> FARPROC {
|
||||
if event != 0x01
|
||||
/* dliNotePreLoadLibrary */
|
||||
{
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
let dll_name = CStr::from_ptr((*info).szDll);
|
||||
if !HOST_BINARIES
|
||||
.iter()
|
||||
.any(|&host_name| host_name == dll_name.to_bytes())
|
||||
{
|
||||
return null_mut();
|
||||
}
|
||||
|
||||
let exe_handle = GetModuleHandleA(null_mut());
|
||||
|
||||
// PfnDliHook sometimes has to return a FARPROC, sometimes an HMODULE, but only one
|
||||
// of them could be specified in the header, so we have to cast our HMODULE to that.
|
||||
exe_handle as FARPROC
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
static mut __pfnDliNotifyHook2: *mut PfnDliHook = load_exe_hook as *mut PfnDliHook;
|
Loading…
Reference in a new issue