diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 56e36ac64..38f9e35b3 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::{braced, token, Token}; use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; use wit_bindgen_rust::{Opts, Ownership, WithOption}; @@ -50,9 +51,9 @@ struct Config { /// The source of the wit package definition enum Source { /// A path to a wit directory - Path(String), + Paths(Vec), /// Inline sources have an optional path to a directory of their dependencies - Inline(String, Option), + Inline(String, Option>), } impl Parse for Config { @@ -71,13 +72,24 @@ impl Parse for Config { match field.into_value() { Opt::Path(s) => { source = Some(match source { - Some(Source::Path(_)) | Some(Source::Inline(_, Some(_))) => { + Some(Source::Paths(_)) | Some(Source::Inline(_, Some(_))) => { return Err(Error::new(s.span(), "cannot specify second source")); } Some(Source::Inline(i, None)) => { - Source::Inline(i, Some(PathBuf::from(s.value()))) + Source::Inline(i, Some(vec![s.value()])) } - None => Source::Path(s.value()), + None => Source::Paths(vec![s.value()]), + }) + } + Opt::Paths(span, p) => { + let paths = p.into_iter().map(|f| f.value()).collect(); + + source = Some(match source { + Some(Source::Paths(_)) | Some(Source::Inline(_, Some(_))) => { + return Err(Error::new(span, "cannot specify second source")); + } + Some(Source::Inline(i, None)) => Source::Inline(i, Some(paths)), + None => Source::Paths(paths), }) } Opt::World(s) => { @@ -91,9 +103,7 @@ impl Parse for Config { Some(Source::Inline(_, _)) => { return Err(Error::new(s.span(), "cannot specify second source")); } - Some(Source::Path(p)) => { - Source::Inline(s.value(), Some(PathBuf::from(p))) - } + Some(Source::Paths(p)) => Source::Inline(s.value(), Some(p)), None => Source::Inline(s.value(), None), }) } @@ -143,7 +153,7 @@ impl Parse for Config { } else { world = input.parse::>()?.map(|s| s.value()); if input.parse::>()?.is_some() { - source = Some(Source::Path(input.parse::()?.value())); + source = Some(Source::Paths(vec![input.parse::()?.value()])); } } let (resolve, pkgs, files) = @@ -168,31 +178,40 @@ fn parse_source( let mut resolve = Resolve::default(); resolve.features.extend(features.iter().cloned()); let mut files = Vec::new(); + let mut pkgs = Vec::new(); let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); - let mut parse = |path: &Path| -> anyhow::Result<_> { - // Try to normalize the path to make the error message more understandable when - // the path is not correct. Fallback to the original path if normalization fails - // (probably return an error somewhere else). - let normalized_path = match std::fs::canonicalize(path) { - Ok(p) => p, - Err(_) => path.to_path_buf(), - }; - let (pkg, sources) = resolve.push_path(normalized_path)?; - files.extend(sources); - Ok(pkg) + let mut parse = |paths: &Vec| -> anyhow::Result<()> { + for path in paths { + let p = root.join(path); + // Try to normalize the path to make the error message more understandable when + // the path is not correct. Fallback to the original path if normalization fails + // (probably return an error somewhere else). + let normalized_path = match std::fs::canonicalize(&p) { + Ok(p) => p, + Err(_) => p.to_path_buf(), + }; + let (pkg, sources) = resolve.push_path(normalized_path)?; + pkgs.extend(pkg); + files.extend(sources); + } + Ok(()) }; - let pkg = match source { + match source { Some(Source::Inline(s, path)) => { if let Some(p) = path { - parse(&root.join(p))?; + parse(p)?; } - resolve.push_group(UnresolvedPackageGroup::parse("macro-input", s)?)? + pkgs = resolve.push_group(UnresolvedPackageGroup::parse("macro-input", s)?)?; } - Some(Source::Path(s)) => parse(&root.join(s))?, - None => parse(&root.join("wit"))?, + Some(Source::Paths(p)) => parse(p)?, + None => parse(&vec![root + .join("wit") + .into_os_string() + .into_string() + .unwrap()])?, }; - Ok((resolve, pkg, files)) + Ok((resolve, pkgs, files)) } impl Config { @@ -250,6 +269,7 @@ mod kw { syn::custom_keyword!(skip); syn::custom_keyword!(world); syn::custom_keyword!(path); + syn::custom_keyword!(paths); syn::custom_keyword!(inline); syn::custom_keyword!(ownership); syn::custom_keyword!(runtime_path); @@ -299,6 +319,7 @@ impl From for wit_bindgen_rust::ExportKey { enum Opt { World(syn::LitStr), Path(syn::LitStr), + Paths(Span, Vec), Inline(syn::LitStr), UseStdFeature, RawStrings, @@ -328,6 +349,13 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::Path(input.parse()?)) + } else if l.peek(kw::paths) { + input.parse::()?; + input.parse::()?; + let contents; + syn::bracketed!(contents in input); + let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?; + Ok(Opt::Paths(list.span(), list.into_iter().collect())) } else if l.peek(kw::inline) { input.parse::()?; input.parse::()?; diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 240f31bf3..1242d3c5e 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -649,6 +649,12 @@ /// // folder adjacent to your `Cargo.toml`. /// path: "../path/to/wit", /// +/// // Multiple paths to parse WIT and its dependencies from. +/// // Usually used in testing, our test suite may want to generate code +/// // from wit files located in multiple paths within a single mod, and we +/// // don't want to copy these files again. +/// paths: ["../path/to/wit1", "../path/to/wit2"], +/// /// // Enables passing "inline WIT". If specified this is the default /// // package that a world is selected from. Any dependencies that this /// // inline WIT refers to must be defined in the `path` option above. diff --git a/crates/test-rust-wasm/Cargo.toml b/crates/test-rust-wasm/Cargo.toml index 63db4d1cb..2c64b1405 100644 --- a/crates/test-rust-wasm/Cargo.toml +++ b/crates/test-rust-wasm/Cargo.toml @@ -52,6 +52,10 @@ test = false name = "owning" test = false +[[bin]] +name = "paths" +test = false + [[bin]] name = "borrowing" test = false diff --git a/crates/test-rust-wasm/src/bin/paths.rs b/crates/test-rust-wasm/src/bin/paths.rs new file mode 100644 index 000000000..73665c16b --- /dev/null +++ b/crates/test-rust-wasm/src/bin/paths.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/paths/wasm.rs"); + +fn main() {} diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 44e928fcd..cde1795fb 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -20,6 +20,7 @@ mod numbers; mod options; mod other_dependencies; mod ownership; +mod paths; mod records; mod resource_aggregates; mod resource_alias; diff --git a/tests/runtime/paths.rs b/tests/runtime/paths.rs new file mode 100644 index 000000000..128a1e1d0 --- /dev/null +++ b/tests/runtime/paths.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use wasmtime::Store; + +// A little bit tricky here, We're not actually testing this, we just want to +// make sure the code in `tests/runtime/paths/wasm.rs` is generated correctly. +wasmtime::component::bindgen!({ + inline: r#" + package test:paths; + + world test { + export paths:path2/test; + } + "#, + path: "tests/runtime/paths/path2" +}); + +#[test] +fn run() -> Result<()> { + crate::run_test( + "paths", + |_linker| Ok(()), + |store, component, linker| Test::instantiate(store, component, linker), + |_exports, _store: &mut Store>| Ok(()), + ) +} diff --git a/tests/runtime/paths/path1/world.wit b/tests/runtime/paths/path1/world.wit new file mode 100644 index 000000000..c879f6009 --- /dev/null +++ b/tests/runtime/paths/path1/world.wit @@ -0,0 +1,3 @@ +package paths:path1; + +interface test {} diff --git a/tests/runtime/paths/path2/world.wit b/tests/runtime/paths/path2/world.wit new file mode 100644 index 000000000..fa102d703 --- /dev/null +++ b/tests/runtime/paths/path2/world.wit @@ -0,0 +1,3 @@ +package paths:path2; + +interface test {} diff --git a/tests/runtime/paths/wasm.rs b/tests/runtime/paths/wasm.rs new file mode 100644 index 000000000..b2fd93cf7 --- /dev/null +++ b/tests/runtime/paths/wasm.rs @@ -0,0 +1,12 @@ +wit_bindgen::generate!({ + inline: r#" + package test:paths; + + world test { + import paths:path1/test; + export paths:path2/test; + } + "#, + paths: ["../../tests/runtime/paths/path1", "../../tests/runtime/paths/path2"], + generate_all, +});