napi_build/lib.rs
1use std::env;
2
3mod android;
4mod wasi;
5mod windows;
6
7/// The build-script lines that give the calling crate `cfg(napi_wasi_threads)`.
8///
9/// The `rustc-check-cfg` line is unconditional, so the cfg is a *known* cfg on
10/// every target and `#[cfg(napi_wasi_threads)]` never trips the
11/// `unexpected_cfgs` lint. The `rustc-cfg` line is emitted only for the
12/// threaded WASI target, matched as an exact triple — the same gate
13/// `crates/napi/build.rs` and `crates/async-runtime/build.rs` use, because the
14/// addon and `napi` have to agree on what the cfg means.
15///
16/// `cargo::` rather than the legacy `cargo:` form: the double-colon syntax
17/// needs Cargo 1.77 and this crate's `rust-version` is 1.88, so a toolchain
18/// old enough to misread it cannot compile `napi-build` in the first place.
19/// Both napi build scripts already emit their cfgs this way.
20fn wasi_threads_cfg_lines(target: &str) -> Vec<&'static str> {
21 let mut lines = vec!["cargo::rustc-check-cfg=cfg(napi_wasi_threads)"];
22 if target == "wasm32-wasip1-threads" {
23 lines.push("cargo::rustc-cfg=napi_wasi_threads");
24 }
25 lines
26}
27
28/// Configure the build of a `napi-rs` addon crate.
29///
30/// Call it from the addon's `build.rs`. Besides the per-platform link
31/// arguments, it hands the crate a first-party `cfg(napi_wasi_threads)`, set
32/// exactly when the crate is compiled for `wasm32-wasip1-threads`:
33///
34/// ```ignore
35/// // build.rs
36/// fn main() {
37/// napi_build::setup();
38/// }
39///
40/// // src/lib.rs
41/// #[cfg(napi_wasi_threads)]
42/// const SHARED_MEMORY: bool = true;
43/// #[cfg(not(napi_wasi_threads))]
44/// const SHARED_MEMORY: bool = false;
45/// ```
46///
47/// rustc has no built-in cfg that tells the two WASI targets apart: it prints
48/// an *identical* cfg set for `wasm32-wasip1` and `wasm32-wasip1-threads`
49/// (same `target_env = "p1"`), and the wasm `atomics` target feature is
50/// unstable, so the stable channel never puts `target_feature = "atomics"` in
51/// the cfg set of either one (rust-lang/rust#77839). Only the exact cargo
52/// `TARGET` answers the question, and only a build script can read it.
53///
54/// A build-script cfg is crate-local: it reaches the crate whose `build.rs`
55/// called this function and nothing else. Every other crate in the graph that
56/// needs the distinction needs its own build script. Absence must therefore be
57/// the conservative branch — a plain `cargo build` compiles the
58/// `not(napi_wasi_threads)` side, and that side has to stay correct.
59pub fn setup() {
60 for line in wasi_threads_cfg_lines(&env::var("TARGET").unwrap_or_default()) {
61 println!("{line}");
62 }
63
64 // compatible with the v2 versions, will remove in the future
65 {
66 println!("cargo:rerun-if-env-changed=DEBUG_GENERATED_CODE");
67 println!("cargo:rerun-if-env-changed=TYPE_DEF_TMP_PATH");
68 println!("cargo:rerun-if-env-changed=CARGO_CFG_NAPI_RS_CLI_VERSION");
69 }
70
71 println!("cargo::rerun-if-env-changed=NAPI_DEBUG_GENERATED_CODE");
72 println!("cargo::rerun-if-env-changed=NAPI_TYPE_DEF_TMP_FOLDER");
73 println!(
74 "cargo::rerun-if-env-changed=NAPI_FORCE_BUILD_{}",
75 env::var("CARGO_PKG_NAME")
76 .expect("CARGO_PKG_NAME is not set")
77 .to_uppercase()
78 .replace("-", "_")
79 );
80
81 let target_env = env::var("CARGO_CFG_TARGET_ENV").expect("CARGO_CFG_TARGET_ENV is not set");
82 let target_os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS is not set");
83
84 match target_os.as_str() {
85 "android" => if android::setup().is_ok() {},
86 "wasi" => {
87 wasi::setup();
88 }
89 "macos" => {
90 // Keep the dynamic lookup behavior on macOS to avoid breaking changes.
91 println!("cargo:rustc-cdylib-link-arg=-Wl");
92 println!("cargo:rustc-cdylib-link-arg=-undefined");
93 println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
94 }
95 "windows" => {
96 if let Ok("gnu") = env::var("CARGO_CFG_TARGET_ENV").as_deref() {
97 windows::setup_gnu();
98 }
99 }
100 _ => {}
101 }
102
103 if (target_env == "gnu" && target_os != "windows")
104 || target_os == "freebsd"
105 || target_os == "openbsd"
106 {
107 // https://sourceware.org/bugzilla/show_bug.cgi?id=21032
108 // https://sourceware.org/bugzilla/show_bug.cgi?id=21031
109 // https://github.com/rust-lang/rust/issues/134820
110 // pthread_key_create() destructors and segfault after a DSO unloading
111 println!("cargo:rustc-link-arg=-Wl,-z,nodelete");
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn emits_the_cfg_for_the_threaded_wasi_target() {
121 assert_eq!(
122 wasi_threads_cfg_lines("wasm32-wasip1-threads"),
123 vec![
124 "cargo::rustc-check-cfg=cfg(napi_wasi_threads)",
125 "cargo::rustc-cfg=napi_wasi_threads",
126 ]
127 );
128 }
129
130 #[test]
131 fn keeps_the_cfg_off_the_threadless_wasi_target() {
132 assert_eq!(
133 wasi_threads_cfg_lines("wasm32-wasip1"),
134 vec!["cargo::rustc-check-cfg=cfg(napi_wasi_threads)"]
135 );
136 }
137
138 #[test]
139 fn still_declares_the_cfg_on_a_native_target() {
140 assert_eq!(
141 wasi_threads_cfg_lines("x86_64-unknown-linux-gnu"),
142 vec!["cargo::rustc-check-cfg=cfg(napi_wasi_threads)"]
143 );
144 }
145}