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