1 use proc_macro2::{Span, TokenStream}; 2 use quote::ToTokens; 3 use std::collections::HashMap; 4 use std::collections::HashSet; 5 use std::env; 6 use std::path::{Path, PathBuf}; 7 use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; 8 use syn::parse::{Error, Parse, ParseStream, Result}; 9 use syn::punctuated::Punctuated; 10 use syn::{braced, token, Token}; 11 use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError, TrappableImports}; 12 use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; 13 14 pub struct Config { 15 opts: Opts, 16 resolve: Resolve, 17 world: WorldId, 18 files: Vec<PathBuf>, 19 include_generated_code_from_file: bool, 20 } 21 22 pub fn expand(input: &Config) -> Result<TokenStream> { 23 if !cfg!(feature = "async") && input.opts.async_.maybe_async() { 24 return Err(Error::new( 25 Span::call_site(), 26 "cannot enable async bindings unless `async` crate feature is active", 27 )); 28 } 29 30 let mut src = match input.opts.generate(&input.resolve, input.world) { 31 Ok(s) => s, 32 Err(e) => return Err(Error::new(Span::call_site(), e.to_string())), 33 }; 34 35 if input.opts.stringify { 36 return Ok(quote::quote!(#src)); 37 } 38 39 // If a magical `WASMTIME_DEBUG_BINDGEN` environment variable is set then 40 // place a formatted version of the expanded code into a file. This file 41 // will then show up in rustc error messages for any codegen issues and can 42 // be inspected manually. 43 if input.include_generated_code_from_file || std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok() { 44 static INVOCATION: AtomicUsize = AtomicUsize::new(0); 45 let root = Path::new(env!("DEBUG_OUTPUT_DIR")); 46 let world_name = &input.resolve.worlds[input.world].name; 47 let n = INVOCATION.fetch_add(1, Relaxed); 48 let path = root.join(format!("{world_name}{n}.rs")); 49 50 std::fs::write(&path, &src).unwrap(); 51 52 // optimistically format the code but don't require success 53 drop( 54 std::process::Command::new("rustfmt") 55 .arg(&path) 56 .arg("--edition=2021") 57 .output(), 58 ); 59 60 src = format!("include!({path:?});"); 61 } 62 let mut contents = src.parse::<TokenStream>().unwrap(); 63 64 // Include a dummy `include_str!` for any files we read so rustc knows that 65 // we depend on the contents of those files. 66 for file in input.files.iter() { 67 contents.extend( 68 format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display()) 69 .parse::<TokenStream>() 70 .unwrap(), 71 ); 72 } 73 74 Ok(contents) 75 } 76 77 impl Parse for Config { 78 fn parse(input: ParseStream<'_>) -> Result<Self> { 79 let call_site = Span::call_site(); 80 let mut opts = Opts::default(); 81 let mut world = None; 82 let mut inline = None; 83 let mut paths = Vec::new(); 84 let mut async_configured = false; 85 let mut include_generated_code_from_file = false; 86 87 if input.peek(token::Brace) { 88 let content; 89 syn::braced!(content in input); 90 let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?; 91 for field in fields.into_pairs() { 92 match field.into_value() { 93 Opt::Path(p) => { 94 paths.extend(p.into_iter().map(|p| p.value())); 95 } 96 Opt::World(s) => { 97 if world.is_some() { 98 return Err(Error::new(s.span(), "cannot specify second world")); 99 } 100 world = Some(s.value()); 101 } 102 Opt::Inline(s) => { 103 if inline.is_some() { 104 return Err(Error::new(s.span(), "cannot specify second source")); 105 } 106 inline = Some(s.value()); 107 } 108 Opt::Tracing(val) => opts.tracing = val, 109 Opt::VerboseTracing(val) => opts.verbose_tracing = val, 110 Opt::Async(val, span) => { 111 if async_configured { 112 return Err(Error::new(span, "cannot specify second async config")); 113 } 114 async_configured = true; 115 opts.async_ = val; 116 } 117 Opt::TrappableErrorType(val) => opts.trappable_error_type = val, 118 Opt::TrappableImports(val) => opts.trappable_imports = val, 119 Opt::Ownership(val) => opts.ownership = val, 120 Opt::Interfaces(s) => { 121 if inline.is_some() { 122 return Err(Error::new(s.span(), "cannot specify a second source")); 123 } 124 inline = Some(format!( 125 " 126 package wasmtime:component-macro-synthesized; 127 128 world interfaces {{ 129 {} 130 }} 131 ", 132 s.value() 133 )); 134 135 if world.is_some() { 136 return Err(Error::new( 137 s.span(), 138 "cannot specify a world with `interfaces`", 139 )); 140 } 141 world = Some("interfaces".to_string()); 142 143 opts.only_interfaces = true; 144 } 145 Opt::With(val) => opts.with.extend(val), 146 Opt::AdditionalDerives(paths) => { 147 opts.additional_derive_attributes = paths 148 .into_iter() 149 .map(|p| p.into_token_stream().to_string()) 150 .collect() 151 } 152 Opt::Stringify(val) => opts.stringify = val, 153 Opt::SkipMutForwardingImpls(val) => opts.skip_mut_forwarding_impls = val, 154 Opt::RequireStoreDataSend(val) => opts.require_store_data_send = val, 155 Opt::WasmtimeCrate(f) => { 156 opts.wasmtime_crate = Some(f.into_token_stream().to_string()) 157 } 158 Opt::IncludeGeneratedCodeFromFile(i) => include_generated_code_from_file = i, 159 } 160 } 161 } else { 162 world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value()); 163 if input.parse::<Option<syn::token::In>>()?.is_some() { 164 paths.push(input.parse::<syn::LitStr>()?.value()); 165 } 166 } 167 let (resolve, pkgs, files) = parse_source(&paths, &inline) 168 .map_err(|err| Error::new(call_site, format!("{err:?}")))?; 169 170 let world = select_world(&resolve, &pkgs, world.as_deref()) 171 .map_err(|e| Error::new(call_site, format!("{e:?}")))?; 172 Ok(Config { 173 opts, 174 resolve, 175 world, 176 files, 177 include_generated_code_from_file, 178 }) 179 } 180 } 181 182 fn parse_source( 183 paths: &Vec<String>, 184 inline: &Option<String>, 185 ) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> { 186 let mut resolve = Resolve::default(); 187 resolve.all_features = true; 188 let mut files = Vec::new(); 189 let mut pkgs = Vec::new(); 190 let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); 191 192 let parse = |resolve: &mut Resolve, 193 files: &mut Vec<PathBuf>, 194 pkgs: &mut Vec<PackageId>, 195 paths: &[String]| 196 -> anyhow::Result<_> { 197 for path in paths { 198 let p = root.join(path); 199 // Try to normalize the path to make the error message more understandable when 200 // the path is not correct. Fallback to the original path if normalization fails 201 // (probably return an error somewhere else). 202 let normalized_path = match std::fs::canonicalize(&p) { 203 Ok(p) => p, 204 Err(_) => p.to_path_buf(), 205 }; 206 let (pkg, sources) = resolve.push_path(normalized_path)?; 207 pkgs.push(pkg); 208 files.extend(sources.paths().map(|p| p.to_owned())); 209 } 210 Ok(()) 211 }; 212 213 if !paths.is_empty() { 214 parse(&mut resolve, &mut files, &mut pkgs, &paths)?; 215 } 216 217 if let Some(inline) = inline { 218 pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?); 219 } 220 221 if pkgs.is_empty() { 222 parse(&mut resolve, &mut files, &mut pkgs, &["wit".into()])?; 223 } 224 225 Ok((resolve, pkgs, files)) 226 } 227 228 fn select_world( 229 resolve: &Resolve, 230 pkgs: &[PackageId], 231 world: Option<&str>, 232 ) -> anyhow::Result<WorldId> { 233 if pkgs.len() == 1 { 234 resolve.select_world(pkgs[0], world) 235 } else { 236 assert!(!pkgs.is_empty()); 237 match world { 238 Some(name) => { 239 if !name.contains(":") { 240 anyhow::bail!( 241 "with multiple packages a fully qualified \ 242 world name must be specified" 243 ) 244 } 245 246 // This will ignore the package argument due to the fully 247 // qualified name being used. 248 resolve.select_world(pkgs[0], world) 249 } 250 None => { 251 let worlds = pkgs 252 .iter() 253 .filter_map(|p| resolve.select_world(*p, None).ok()) 254 .collect::<Vec<_>>(); 255 match &worlds[..] { 256 [] => anyhow::bail!("no packages have a world"), 257 [world] => Ok(*world), 258 _ => anyhow::bail!("multiple packages have a world, must specify which to use"), 259 } 260 } 261 } 262 } 263 } 264 265 mod kw { 266 syn::custom_keyword!(inline); 267 syn::custom_keyword!(path); 268 syn::custom_keyword!(tracing); 269 syn::custom_keyword!(verbose_tracing); 270 syn::custom_keyword!(trappable_error_type); 271 syn::custom_keyword!(world); 272 syn::custom_keyword!(ownership); 273 syn::custom_keyword!(interfaces); 274 syn::custom_keyword!(with); 275 syn::custom_keyword!(except_imports); 276 syn::custom_keyword!(only_imports); 277 syn::custom_keyword!(trappable_imports); 278 syn::custom_keyword!(additional_derives); 279 syn::custom_keyword!(stringify); 280 syn::custom_keyword!(skip_mut_forwarding_impls); 281 syn::custom_keyword!(require_store_data_send); 282 syn::custom_keyword!(wasmtime_crate); 283 syn::custom_keyword!(include_generated_code_from_file); 284 } 285 286 enum Opt { 287 World(syn::LitStr), 288 Path(Vec<syn::LitStr>), 289 Inline(syn::LitStr), 290 Tracing(bool), 291 VerboseTracing(bool), 292 Async(AsyncConfig, Span), 293 TrappableErrorType(Vec<TrappableError>), 294 Ownership(Ownership), 295 Interfaces(syn::LitStr), 296 With(HashMap<String, String>), 297 TrappableImports(TrappableImports), 298 AdditionalDerives(Vec<syn::Path>), 299 Stringify(bool), 300 SkipMutForwardingImpls(bool), 301 RequireStoreDataSend(bool), 302 WasmtimeCrate(syn::Path), 303 IncludeGeneratedCodeFromFile(bool), 304 } 305 306 impl Parse for Opt { 307 fn parse(input: ParseStream<'_>) -> Result<Self> { 308 let l = input.lookahead1(); 309 if l.peek(kw::path) { 310 input.parse::<kw::path>()?; 311 input.parse::<Token![:]>()?; 312 313 let mut paths: Vec<syn::LitStr> = vec![]; 314 315 let l = input.lookahead1(); 316 if l.peek(syn::LitStr) { 317 paths.push(input.parse()?); 318 } else if l.peek(syn::token::Bracket) { 319 let contents; 320 syn::bracketed!(contents in input); 321 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?; 322 323 paths.extend(list.into_iter()); 324 } else { 325 return Err(l.error()); 326 }; 327 328 Ok(Opt::Path(paths)) 329 } else if l.peek(kw::inline) { 330 input.parse::<kw::inline>()?; 331 input.parse::<Token![:]>()?; 332 Ok(Opt::Inline(input.parse()?)) 333 } else if l.peek(kw::world) { 334 input.parse::<kw::world>()?; 335 input.parse::<Token![:]>()?; 336 Ok(Opt::World(input.parse()?)) 337 } else if l.peek(kw::tracing) { 338 input.parse::<kw::tracing>()?; 339 input.parse::<Token![:]>()?; 340 Ok(Opt::Tracing(input.parse::<syn::LitBool>()?.value)) 341 } else if l.peek(kw::verbose_tracing) { 342 input.parse::<kw::verbose_tracing>()?; 343 input.parse::<Token![:]>()?; 344 Ok(Opt::VerboseTracing(input.parse::<syn::LitBool>()?.value)) 345 } else if l.peek(Token![async]) { 346 let span = input.parse::<Token![async]>()?.span; 347 input.parse::<Token![:]>()?; 348 if input.peek(syn::LitBool) { 349 match input.parse::<syn::LitBool>()?.value { 350 true => Ok(Opt::Async(AsyncConfig::All, span)), 351 false => Ok(Opt::Async(AsyncConfig::None, span)), 352 } 353 } else { 354 let contents; 355 syn::braced!(contents in input); 356 357 let l = contents.lookahead1(); 358 let ctor: fn(HashSet<String>) -> AsyncConfig = if l.peek(kw::except_imports) { 359 contents.parse::<kw::except_imports>()?; 360 contents.parse::<Token![:]>()?; 361 AsyncConfig::AllExceptImports 362 } else if l.peek(kw::only_imports) { 363 contents.parse::<kw::only_imports>()?; 364 contents.parse::<Token![:]>()?; 365 AsyncConfig::OnlyImports 366 } else { 367 return Err(l.error()); 368 }; 369 370 let list; 371 syn::bracketed!(list in contents); 372 let fields: Punctuated<syn::LitStr, Token![,]> = 373 list.parse_terminated(Parse::parse, Token![,])?; 374 375 if contents.peek(Token![,]) { 376 contents.parse::<Token![,]>()?; 377 } 378 Ok(Opt::Async( 379 ctor(fields.iter().map(|s| s.value()).collect()), 380 span, 381 )) 382 } 383 } else if l.peek(kw::ownership) { 384 input.parse::<kw::ownership>()?; 385 input.parse::<Token![:]>()?; 386 let ownership = input.parse::<syn::Ident>()?; 387 Ok(Opt::Ownership(match ownership.to_string().as_str() { 388 "Owning" => Ownership::Owning, 389 "Borrowing" => Ownership::Borrowing { 390 duplicate_if_necessary: { 391 let contents; 392 braced!(contents in input); 393 let field = contents.parse::<syn::Ident>()?; 394 match field.to_string().as_str() { 395 "duplicate_if_necessary" => { 396 contents.parse::<Token![:]>()?; 397 contents.parse::<syn::LitBool>()?.value 398 } 399 name => { 400 return Err(Error::new( 401 field.span(), 402 format!( 403 "unrecognized `Ownership::Borrowing` field: `{name}`; \ 404 expected `duplicate_if_necessary`" 405 ), 406 )); 407 } 408 } 409 }, 410 }, 411 name => { 412 return Err(Error::new( 413 ownership.span(), 414 format!( 415 "unrecognized ownership: `{name}`; \ 416 expected `Owning` or `Borrowing`" 417 ), 418 )); 419 } 420 })) 421 } else if l.peek(kw::trappable_error_type) { 422 input.parse::<kw::trappable_error_type>()?; 423 input.parse::<Token![:]>()?; 424 let contents; 425 let _lbrace = braced!(contents in input); 426 let fields: Punctuated<_, Token![,]> = 427 contents.parse_terminated(trappable_error_field_parse, Token![,])?; 428 Ok(Opt::TrappableErrorType(Vec::from_iter(fields))) 429 } else if l.peek(kw::interfaces) { 430 input.parse::<kw::interfaces>()?; 431 input.parse::<Token![:]>()?; 432 Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?)) 433 } else if l.peek(kw::with) { 434 input.parse::<kw::with>()?; 435 input.parse::<Token![:]>()?; 436 let contents; 437 let _lbrace = braced!(contents in input); 438 let fields: Punctuated<(String, String), Token![,]> = 439 contents.parse_terminated(with_field_parse, Token![,])?; 440 Ok(Opt::With(HashMap::from_iter(fields))) 441 } else if l.peek(kw::trappable_imports) { 442 input.parse::<kw::trappable_imports>()?; 443 input.parse::<Token![:]>()?; 444 let config = if input.peek(syn::LitBool) { 445 match input.parse::<syn::LitBool>()?.value { 446 true => TrappableImports::All, 447 false => TrappableImports::None, 448 } 449 } else { 450 let contents; 451 syn::bracketed!(contents in input); 452 let fields: Punctuated<syn::LitStr, Token![,]> = 453 contents.parse_terminated(Parse::parse, Token![,])?; 454 TrappableImports::Only(fields.iter().map(|s| s.value()).collect()) 455 }; 456 Ok(Opt::TrappableImports(config)) 457 } else if l.peek(kw::additional_derives) { 458 input.parse::<kw::additional_derives>()?; 459 input.parse::<Token![:]>()?; 460 let contents; 461 syn::bracketed!(contents in input); 462 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?; 463 Ok(Opt::AdditionalDerives(list.iter().cloned().collect())) 464 } else if l.peek(kw::stringify) { 465 input.parse::<kw::stringify>()?; 466 input.parse::<Token![:]>()?; 467 Ok(Opt::Stringify(input.parse::<syn::LitBool>()?.value)) 468 } else if l.peek(kw::skip_mut_forwarding_impls) { 469 input.parse::<kw::skip_mut_forwarding_impls>()?; 470 input.parse::<Token![:]>()?; 471 Ok(Opt::SkipMutForwardingImpls( 472 input.parse::<syn::LitBool>()?.value, 473 )) 474 } else if l.peek(kw::require_store_data_send) { 475 input.parse::<kw::require_store_data_send>()?; 476 input.parse::<Token![:]>()?; 477 Ok(Opt::RequireStoreDataSend( 478 input.parse::<syn::LitBool>()?.value, 479 )) 480 } else if l.peek(kw::wasmtime_crate) { 481 input.parse::<kw::wasmtime_crate>()?; 482 input.parse::<Token![:]>()?; 483 Ok(Opt::WasmtimeCrate(input.parse()?)) 484 } else if l.peek(kw::include_generated_code_from_file) { 485 input.parse::<kw::include_generated_code_from_file>()?; 486 input.parse::<Token![:]>()?; 487 Ok(Opt::IncludeGeneratedCodeFromFile( 488 input.parse::<syn::LitBool>()?.value, 489 )) 490 } else { 491 Err(l.error()) 492 } 493 } 494 } 495 496 fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> { 497 let wit_path = input.parse::<syn::LitStr>()?.value(); 498 input.parse::<Token![=>]>()?; 499 let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string(); 500 Ok(TrappableError { 501 wit_path, 502 rust_type_name, 503 }) 504 } 505 506 fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> { 507 let interface = input.parse::<syn::LitStr>()?.value(); 508 input.parse::<Token![:]>()?; 509 let start = input.span(); 510 let path = input.parse::<syn::Path>()?; 511 512 // It's not possible for the segments of a path to be empty 513 let span = start 514 .join(path.segments.last().unwrap().ident.span()) 515 .unwrap_or(start); 516 517 let mut buf = String::new(); 518 let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> { 519 if segment.arguments != syn::PathArguments::None { 520 return Err(Error::new( 521 span, 522 "Module path must not contain angles or parens", 523 )); 524 } 525 526 buf.push_str(&segment.ident.to_string()); 527 528 Ok(()) 529 }; 530 531 if path.leading_colon.is_some() { 532 buf.push_str("::"); 533 } 534 535 let mut segments = path.segments.into_iter(); 536 537 if let Some(segment) = segments.next() { 538 append(&mut buf, segment)?; 539 } 540 541 for segment in segments { 542 buf.push_str("::"); 543 append(&mut buf, segment)?; 544 } 545 546 Ok((interface, buf)) 547 } 548