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