1 | use std::env; |
2 | use std::fs; |
3 | use std::io::BufWriter; |
4 | use std::io::Write; |
5 | use std::sync::atomic::{AtomicBool, Ordering}; |
6 | |
7 | use crate::parser::{attrs::BindgenAttrs, ParseNapi}; |
8 | #[cfg (feature = "type-def" )] |
9 | use napi_derive_backend::ToTypeDef; |
10 | use napi_derive_backend::{BindgenResult, Napi, TryToTokens}; |
11 | use proc_macro2::TokenStream; |
12 | use quote::ToTokens; |
13 | use syn::{Attribute, Item}; |
14 | |
15 | /// a flag indicate whether or never at least one `napi` macro has been expanded. |
16 | /// ```ignore |
17 | /// if BUILT_FLAG |
18 | /// .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) |
19 | /// .is_ok() { |
20 | /// // logic on first macro expansion |
21 | /// } |
22 | /// |
23 | /// ``` |
24 | static BUILT_FLAG: AtomicBool = AtomicBool::new(false); |
25 | |
26 | pub fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult<TokenStream> { |
27 | if BUILT_FLAG |
28 | .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) |
29 | .is_ok() |
30 | { |
31 | // logic on first macro expansion |
32 | #[cfg (feature = "type-def" )] |
33 | prepare_type_def_file(); |
34 | |
35 | if let Ok(wasi_register_file) = env::var("WASI_REGISTER_TMP_PATH" ) { |
36 | if let Err(_e) = remove_existed_def_file(&wasi_register_file) { |
37 | #[cfg (debug_assertions)] |
38 | { |
39 | println!("Failed to manipulate wasi register file: {:?}" , _e); |
40 | } |
41 | } |
42 | } |
43 | } |
44 | |
45 | let mut item = syn::parse2::<Item>(input)?; |
46 | let opts: BindgenAttrs = syn::parse2(attr)?; |
47 | let mut tokens = proc_macro2::TokenStream::new(); |
48 | if let Item::Mod(mut js_mod) = item { |
49 | let js_name = opts.js_name().map_or_else( |
50 | || js_mod.ident.to_string(), |
51 | |(js_name, _)| js_name.to_owned(), |
52 | ); |
53 | if let Some((_, mut items)) = js_mod.content.clone() { |
54 | for item in items.iter_mut() { |
55 | let mut empty_attrs = vec![]; |
56 | if let Some(item_opts) = replace_napi_attr_in_mod( |
57 | js_name.clone(), |
58 | match item { |
59 | Item::Fn(ref mut function) => &mut function.attrs, |
60 | Item::Struct(ref mut struct_) => &mut struct_.attrs, |
61 | Item::Enum(ref mut enum_) => &mut enum_.attrs, |
62 | Item::Const(ref mut const_) => &mut const_.attrs, |
63 | Item::Impl(ref mut impl_) => &mut impl_.attrs, |
64 | Item::Mod(mod_) => { |
65 | let mod_in_mod = mod_ |
66 | .attrs |
67 | .iter() |
68 | .enumerate() |
69 | .find(|(_, m)| m.path().is_ident("napi" )); |
70 | if mod_in_mod.is_some() { |
71 | bail_span!( |
72 | mod_, |
73 | "napi module cannot be nested under another napi module" |
74 | ); |
75 | } else { |
76 | &mut empty_attrs |
77 | } |
78 | } |
79 | _ => &mut empty_attrs, |
80 | }, |
81 | ) { |
82 | let napi = item.parse_napi(&mut tokens, &item_opts)?; |
83 | item_opts.check_used()?; |
84 | napi.try_to_tokens(&mut tokens)?; |
85 | |
86 | #[cfg (feature = "type-def" )] |
87 | output_type_def(&napi); |
88 | output_wasi_register_def(&napi); |
89 | } else { |
90 | item.to_tokens(&mut tokens); |
91 | }; |
92 | } |
93 | js_mod.content = None; |
94 | }; |
95 | |
96 | let js_mod_attrs: Vec<Attribute> = js_mod |
97 | .attrs |
98 | .clone() |
99 | .into_iter() |
100 | .filter(|attr| attr.path().is_ident("napi" )) |
101 | .collect(); |
102 | let mod_name = js_mod.ident; |
103 | let visible = js_mod.vis; |
104 | let mod_tokens = quote! { #(#js_mod_attrs)* #visible mod #mod_name { #tokens } }; |
105 | Ok(mod_tokens) |
106 | } else { |
107 | let napi = item.parse_napi(&mut tokens, &opts)?; |
108 | opts.check_used()?; |
109 | napi.try_to_tokens(&mut tokens)?; |
110 | |
111 | #[cfg (feature = "type-def" )] |
112 | output_type_def(&napi); |
113 | output_wasi_register_def(&napi); |
114 | Ok(tokens) |
115 | } |
116 | } |
117 | |
118 | fn output_wasi_register_def(napi: &Napi) { |
119 | if let Ok(wasi_register_file: String) = env::var(key:"WASI_REGISTER_TMP_PATH" ) { |
120 | fs::OpenOptions::new() |
121 | .append(true) |
122 | .create(true) |
123 | .open(wasi_register_file) |
124 | .and_then(|file| { |
125 | let mut writer = BufWriter::<fs::File>::new(file); |
126 | let pkg_name: String = std::env::var("CARGO_PKG_NAME" ).expect("CARGO_PKG_NAME is not set" ); |
127 | writer.write_all(format!(" {pkg_name}: {}" , napi.register_name()).as_bytes())?; |
128 | writer.write_all(" \n" .as_bytes())?; |
129 | writer.flush() |
130 | }) |
131 | .unwrap_or_else(|e: Error| { |
132 | println!("Failed to write wasi register file: {:?}" , e); |
133 | }); |
134 | } |
135 | } |
136 | |
137 | #[cfg (feature = "type-def" )] |
138 | fn output_type_def(napi: &Napi) { |
139 | if let Ok(type_def_file: String) = env::var(key:"TYPE_DEF_TMP_PATH" ) { |
140 | if let Some(type_def: TypeDef) = napi.to_type_def() { |
141 | fs::OpenOptions::new() |
142 | .append(true) |
143 | .create(true) |
144 | .open(type_def_file) |
145 | .and_then(|file| { |
146 | let mut writer = BufWriter::<fs::File>::new(file); |
147 | writer.write_all(type_def.to_string().as_bytes())?; |
148 | writer.write_all(" \n" .as_bytes())?; |
149 | writer.flush() |
150 | }) |
151 | .unwrap_or_else(|e: Error| { |
152 | println!("Failed to write type def file: {:?}" , e); |
153 | }); |
154 | } |
155 | } |
156 | } |
157 | |
158 | fn replace_napi_attr_in_mod( |
159 | js_namespace: String, |
160 | attrs: &mut Vec<syn::Attribute>, |
161 | ) -> Option<BindgenAttrs> { |
162 | let napi_attr = attrs |
163 | .iter() |
164 | .enumerate() |
165 | .find(|(_, m)| m.path().is_ident("napi" )); |
166 | |
167 | if let Some((index, napi_attr)) = napi_attr { |
168 | // adds `namespace = #js_namespace` into `#[napi]` attribute |
169 | let new_attr = match &napi_attr.meta { |
170 | syn::Meta::Path(_) => { |
171 | syn::parse_quote!(#[napi(namespace = #js_namespace)]) |
172 | } |
173 | syn::Meta::List(list) => { |
174 | let existing = list.tokens.clone(); |
175 | syn::parse_quote!(#[napi(#existing, namespace = #js_namespace)]) |
176 | } |
177 | syn::Meta::NameValue(name_value) => { |
178 | let existing = &name_value.value; |
179 | syn::parse_quote!(#[napi(#existing, namespace = #js_namespace)]) |
180 | } |
181 | }; |
182 | |
183 | let struct_opts = BindgenAttrs::try_from(&new_attr).unwrap(); |
184 | attrs.remove(index); |
185 | Some(struct_opts) |
186 | } else { |
187 | None |
188 | } |
189 | } |
190 | |
191 | #[cfg (feature = "type-def" )] |
192 | fn prepare_type_def_file() { |
193 | if let Ok(ref type_def_file: &String) = env::var(key:"TYPE_DEF_TMP_PATH" ) { |
194 | use napi_derive_backend::{NAPI_RS_CLI_VERSION, NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX}; |
195 | if let Err(_e: Error) = if *NAPI_RS_CLI_VERSION >= *NAPI_RS_CLI_VERSION_WITH_SHARED_CRATES_FIX { |
196 | remove_existed_def_file(type_def_file) |
197 | } else { |
198 | fs::remove_file(path:type_def_file) |
199 | } { |
200 | #[cfg (debug_assertions)] |
201 | { |
202 | println!("Failed to manipulate type def file: {:?}" , _e); |
203 | } |
204 | } |
205 | } |
206 | } |
207 | |
208 | fn remove_existed_def_file(def_file: &str) -> std::io::Result<()> { |
209 | use std::io::{BufRead, BufReader}; |
210 | |
211 | let pkg_name = std::env::var("CARGO_PKG_NAME" ).expect("CARGO_PKG_NAME is not set" ); |
212 | if let Ok(content) = std::fs::File::open(def_file) { |
213 | let reader = BufReader::new(content); |
214 | let cleaned_content = reader |
215 | .lines() |
216 | .filter_map(|line| { |
217 | if let Ok(line) = line { |
218 | if let Some((package_name, _)) = line.split_once(':' ) { |
219 | if pkg_name == package_name { |
220 | return None; |
221 | } |
222 | } |
223 | Some(line) |
224 | } else { |
225 | None |
226 | } |
227 | }) |
228 | .collect::<Vec<String>>() |
229 | .join(" \n" ); |
230 | std::fs::write(def_file, format!(" {cleaned_content}\n" ))?; |
231 | } |
232 | Ok(()) |
233 | } |
234 | |