Skip to content

Commit

Permalink
feat: component bindgen: add support for multiple wit paths (#9288)
Browse files Browse the repository at this point in the history
* feat: bindgen: add support for multiple wit paths

* expanded tests

* fmt

---------

Co-authored-by: Lochlan Wansbrough <>
  • Loading branch information
lwansbrough authored Sep 24, 2024
1 parent 93a1f08 commit 80d678a
Show file tree
Hide file tree
Showing 10 changed files with 1,427 additions and 38 deletions.
134 changes: 96 additions & 38 deletions crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl Parse for Config {
let mut opts = Opts::default();
let mut world = None;
let mut inline = None;
let mut path = None;
let mut paths = Vec::new();
let mut async_configured = false;
let mut features = Vec::new();
let mut include_generated_code_from_file = false;
Expand All @@ -91,11 +91,8 @@ impl Parse for Config {
let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
for field in fields.into_pairs() {
match field.into_value() {
Opt::Path(s) => {
if path.is_some() {
return Err(Error::new(s.span(), "cannot specify second path"));
}
path = Some(s.value());
Opt::Path(p) => {
paths.extend(p.into_iter().map(|p| p.value()));
}
Opt::World(s) => {
if world.is_some() {
Expand Down Expand Up @@ -168,14 +165,13 @@ impl Parse for Config {
} else {
world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());
if input.parse::<Option<syn::token::In>>()?.is_some() {
path = Some(input.parse::<syn::LitStr>()?.value());
paths.push(input.parse::<syn::LitStr>()?.value());
}
}
let (resolve, pkg, files) = parse_source(&path, &inline, &features)
let (resolve, pkgs, files) = parse_source(&paths, &inline, &features)
.map_err(|err| Error::new(call_site, format!("{err:?}")))?;

let world = resolve
.select_world(pkg, world.as_deref())
let world = select_world(&resolve, &pkgs, world.as_deref())
.map_err(|e| Error::new(call_site, format!("{e:?}")))?;
Ok(Config {
opts,
Expand All @@ -188,47 +184,89 @@ impl Parse for Config {
}

fn parse_source(
path: &Option<String>,
paths: &Vec<String>,
inline: &Option<String>,
features: &[String],
) -> anyhow::Result<(Resolve, PackageId, Vec<PathBuf>)> {
) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {
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 = |resolve: &mut Resolve, 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 parse = |resolve: &mut Resolve,
files: &mut Vec<PathBuf>,
pkgs: &mut Vec<PackageId>,
paths: &[String]|
-> 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.push(pkg);
files.extend(sources);
}
Ok(())
};

let path_pkg = if let Some(path) = path {
Some(parse(&mut resolve, &root.join(path))?)
} else {
None
};
if !paths.is_empty() {
parse(&mut resolve, &mut files, &mut pkgs, &paths)?;
}

let inline_pkgs = if let Some(inline) = inline {
Some(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?)
} else {
None
};
if let Some(inline) = inline {
pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?);
}

let pkgs = inline_pkgs
.or(path_pkg)
.map_or_else(|| parse(&mut resolve, &root.join("wit")), Ok)?;
if pkgs.is_empty() {
parse(&mut resolve, &mut files, &mut pkgs, &["wit".into()])?;
}

Ok((resolve, pkgs, files))
}

fn select_world(
resolve: &Resolve,
pkgs: &[PackageId],
world: Option<&str>,
) -> anyhow::Result<WorldId> {
if pkgs.len() == 1 {
resolve.select_world(pkgs[0], world)
} else {
assert!(!pkgs.is_empty());
match world {
Some(name) => {
if !name.contains(":") {
anyhow::bail!(
"with multiple packages a fully qualified \
world name must be specified"
)
}

// This will ignore the package argument due to the fully
// qualified name being used.
resolve.select_world(pkgs[0], world)
}
None => {
let worlds = pkgs
.iter()
.filter_map(|p| resolve.select_world(*p, None).ok())
.collect::<Vec<_>>();
match &worlds[..] {
[] => anyhow::bail!("no packages have a world"),
[world] => Ok(*world),
_ => anyhow::bail!("multiple packages have a world, must specify which to use"),
}
}
}
}
}

mod kw {
syn::custom_keyword!(inline);
syn::custom_keyword!(path);
Expand All @@ -253,7 +291,7 @@ mod kw {

enum Opt {
World(syn::LitStr),
Path(syn::LitStr),
Path(Vec<syn::LitStr>),
Inline(syn::LitStr),
Tracing(bool),
VerboseTracing(bool),
Expand All @@ -278,7 +316,27 @@ impl Parse for Opt {
if l.peek(kw::path) {
input.parse::<kw::path>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Path(input.parse()?))

let mut paths: Vec<syn::LitStr> = vec![];

let l = input.lookahead1();
if l.peek(syn::LitStr) {
paths.push(input.parse()?);
} else if l.peek(syn::token::Bracket) {
let contents;
syn::bracketed!(contents in input);
let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;

paths.extend(list.into_iter());

if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
} else {
return Err(l.error());
};

Ok(Opt::Path(paths))
} else if l.peek(kw::inline) {
input.parse::<kw::inline>()?;
input.parse::<Token![:]>()?;
Expand Down
17 changes: 17 additions & 0 deletions crates/component-macro/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ mod with_key_and_resources {

mod trappable_errors_with_versioned_and_unversioned_packages {
wasmtime::component::bindgen!({
world: "foo:foo/nope",
inline: "
package foo:[email protected];
Expand Down Expand Up @@ -697,3 +698,19 @@ mod trappable_error_type_and_versions {
});
}
}

mod paths {
mod multiple_paths {
wasmtime::component::bindgen!({
world: "test:paths/test",
inline: r#"
package test:paths;
world test {
import paths:path1/test;
export paths:path2/test;
}
"#,
path: ["tests/codegen/path1", "tests/codegen/path2"],
});
}
}
7 changes: 7 additions & 0 deletions crates/component-macro/tests/codegen/path1/world.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package paths:path1;

interface test {}

world path1 {
import test;
}
7 changes: 7 additions & 0 deletions crates/component-macro/tests/codegen/path2/world.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package paths:path2;

interface test {}

world path2 {
import test;
}
Loading

0 comments on commit 80d678a

Please sign in to comment.