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