Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: component bindgen: add support for multiple wit paths #9288

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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