nj_build/
lib.rs

1mod win_delay_load_hook;
2
3#[cfg(windows)]
4mod win {
5    use super::win_delay_load_hook;
6    use std::env::var;
7    use std::fs::{File, remove_file};
8    use http_req::request;
9    use std::{
10        process::Command,
11        env::temp_dir,
12        io,
13        path::{PathBuf},
14        fs::{read_dir, create_dir, DirEntry},
15        time::{SystemTime, UNIX_EPOCH},
16    };
17
18    macro_rules! cargo_warn {
19        ($($tokens: tt)*) => {
20            println!("cargo:warning={}", format!($($tokens)*))
21        }
22    }
23
24    fn print_folder(path: &PathBuf) {
25        match ls(path) {
26            Ok(list) => {
27                list.iter()
28                    .for_each(|entry| cargo_warn!("{:?}", entry.path()));
29            }
30            Err(err) => cargo_warn!("Fail read {path:?}: {err}"),
31        }
32    }
33
34    fn ls(path: &PathBuf) -> Result<Vec<DirEntry>, io::Error> {
35        let mut list = vec![];
36        for entry in read_dir(path)? {
37            list.push(entry?);
38        }
39        Ok(list)
40    }
41
42    fn tmp_folder_name() -> String {
43        format!(
44            "node_bindgen_build_{}",
45            SystemTime::now()
46                .duration_since(UNIX_EPOCH)
47                .expect("Time went backwards")
48                .as_nanos()
49        )
50    }
51
52    fn find_file_ends_with(path: &PathBuf, filename: &str) -> Option<String> {
53        let list = ls(path).expect("Get list of files from temp folder");
54        list.iter()
55            .find(|entry| {
56                if let Ok(mt) = entry.metadata() {
57                    if mt.is_file() {
58                        return entry.file_name().to_string_lossy().ends_with(filename);
59                    }
60                }
61                false
62            })
63            .map(|entry| entry.file_name().to_string_lossy().to_string())
64    }
65
66    pub fn configure() {
67        // On Windows, we need to download the dynamic library from the nodejs.org website first
68        let node_full_version =
69            String::from_utf8(Command::new("node").arg("-v").output().unwrap().stdout)
70                .unwrap()
71                .trim_end()
72                .to_string();
73
74        let tmp_dir = temp_dir().join(tmp_folder_name());
75        if !tmp_dir.exists() {
76            create_dir(&tmp_dir).expect("Folder {tmp_dir:?} would be created");
77        }
78        let temp_lib = tmp_dir.join(format!("node-{}.lib", node_full_version));
79
80        if !temp_lib.exists() {
81            let lib_file_download_url = format!(
82                "https://nodejs.org/dist/{}/win-x64/node.lib",
83                node_full_version
84            );
85
86            cargo_warn!(
87                "downloading nodejs: {} to: {:#?}",
88                lib_file_download_url,
89                temp_lib
90            );
91
92            let mut node_lib_file = File::create(&temp_lib).unwrap();
93            if let Err(err) = request::get(&lib_file_download_url, &mut node_lib_file) {
94                if temp_lib.exists() {
95                    if let Err(err) = remove_file(&temp_lib) {
96                        cargo_warn!("Fail to remove {:#?} due error: {}", temp_lib, err);
97                    }
98                }
99                panic!("Download node.lib file failed with: {}", err);
100            };
101        }
102
103        println!(
104            "cargo:rustc-link-lib={}",
105            &temp_lib.file_stem().unwrap().to_str().unwrap()
106        );
107        println!("cargo:rustc-link-search={}", tmp_dir.to_str().unwrap());
108
109        // Link `win_delay_load_hook.obj` for windows electron
110        let node_runtime_env = "npm_config_runtime";
111        println!("cargo:rerun-if-env-changed={}", node_runtime_env);
112
113        if var(node_runtime_env).map(|s| s == "electron") == Ok(true) {
114            // Build win_delay_load_hook
115            let mut filename = format!(
116                "{}.o",
117                win_delay_load_hook::build(tmp_dir.clone())
118                    .expect("Failed to build win_delay_load_hook")
119            );
120            let full_filename = tmp_dir.join(&filename);
121            // Checking for object file
122            if !full_filename.exists() {
123                cargo_warn!("File {full_filename:?} doesn't exist");
124                // Drop into logs list of generated files
125                print_folder(&tmp_dir);
126                // It might be a target file is generated, but the name includes a prefix
127                cargo_warn!("Looking for file {filename} with some prefix in {tmp_dir:?}");
128                if let Some(prefix_filename) = find_file_ends_with(&tmp_dir, &filename) {
129                    filename = prefix_filename;
130                    cargo_warn!("Found object file {filename}");
131                } else {
132                    panic!("Fail to find any related object file");
133                }
134            }
135            println!("cargo:rustc-cdylib-link-arg={filename}");
136            println!("cargo:rustc-cdylib-link-arg=delayimp.lib");
137            println!("cargo:rustc-cdylib-link-arg=/DELAYLOAD:node.exe");
138            println!("cargo:rustc-cdylib-link-arg=/INCLUDE:__pfnDliNotifyHook2");
139            println!("cargo:rustc-cdylib-link-arg=/FORCE:MULTIPLE");
140        }
141    }
142}
143
144/// Slightly modified from https://github.com/Brooooooklyn/napi-rs/blob/master/build/src/lib.rs
145/// configure linker to generate node.js dynamic library
146#[cfg(windows)]
147pub fn configure() {
148    win::configure();
149}
150
151#[cfg(unix)]
152pub fn configure() {
153    if cfg!(target_os = "macos") {
154        // Set up the build environment by setting Cargo configuration variables.
155        println!("cargo:rustc-cdylib-link-arg=-undefined");
156        println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
157    }
158
159    // On Linux, no additional configuration is needed
160}