From 324fc03ddc6b0da0de9c7cd418875bd9d0c57307 Mon Sep 17 00:00:00 2001 From: Ho Kim Date: Mon, 22 Sep 2025 17:46:15 +0000 Subject: [PATCH] feat: add support for multiple wit paths Signed-off-by: Ho Kim --- src/command.rs | 21 +++++++++--------- src/lib.rs | 59 ++++++++++++++++++++++++++++++++------------------ src/python.rs | 6 ++--- src/test.rs | 2 +- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/command.rs b/src/command.rs index fe75668..662a575 100644 --- a/src/command.rs +++ b/src/command.rs @@ -24,9 +24,12 @@ pub struct Options { #[derive(clap::Args, Clone, Debug)] pub struct Common { - /// File or directory containing WIT document(s) + /// File or directory containing WIT document(s). + /// + /// This may be specified more than once, for example: + /// `-d ./wit/deps -d ./wit/app` #[arg(short = 'd', long)] - pub wit_path: Option, + pub wit_path: Vec, /// Name of world to target (or default world if `None`) #[arg(short = 'w', long)] @@ -151,9 +154,7 @@ pub fn run + Clone, I: IntoIterator>(args: I) -> Res fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> { crate::generate_bindings( - &common - .wit_path - .unwrap_or_else(|| Path::new("wit").to_owned()), + &common.wit_path, common.world.as_deref(), &common.features, common.all_features, @@ -185,7 +186,7 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> { } Runtime::new()?.block_on(crate::componentize( - common.wit_path.as_deref(), + &common.wit_path, common.world.as_deref(), &common.features, common.all_features, @@ -338,7 +339,7 @@ mod tests { // When generating the bindings for this WIT world let common = Common { - wit_path: Some(wit.path().into()), + wit_path: vec![wit.path().into()], world: None, world_module: Some("bindings".into()), quiet: false, @@ -368,7 +369,7 @@ mod tests { // When generating the bindings for this WIT world let common = Common { - wit_path: Some(wit.path().into()), + wit_path: vec![wit.path().into()], world: None, world_module: Some("bindings".into()), quiet: false, @@ -398,7 +399,7 @@ mod tests { // When generating the bindings for this WIT world let common = Common { - wit_path: Some(wit.path().into()), + wit_path: vec![wit.path().into()], world: None, world_module: Some("bindings".into()), quiet: false, @@ -426,7 +427,7 @@ mod tests { let wit = gated_x_wit_file()?; let out_dir = tempfile::tempdir()?; let common = Common { - wit_path: Some(wit.path().into()), + wit_path: vec![wit.path().into()], world: None, world_module: Some("bindings".into()), quiet: false, diff --git a/src/lib.rs b/src/lib.rs index 5a8685b..a5a7645 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,7 +182,7 @@ impl Invoker for MyInvoker { #[allow(clippy::too_many_arguments)] pub fn generate_bindings( - wit_path: &Path, + wit_path: &[impl AsRef], world: Option<&str>, features: &[String], all_features: bool, @@ -217,7 +217,7 @@ pub fn generate_bindings( #[allow(clippy::type_complexity, clippy::too_many_arguments)] pub async fn componentize( - wit_path: Option<&Path>, + wit_path: &[impl AsRef], world: Option<&str>, features: &[String], all_features: bool, @@ -245,11 +245,12 @@ pub async fn componentize( // Next, iterate over all the WIT directories, merging them into a single `Resolve`, and matching Python // packages to `WorldId`s. - let (mut resolve, mut main_world) = if let Some(path) = wit_path { - let (resolve, world) = parse_wit(path, world, features, all_features)?; - (Some(resolve), Some(world)) - } else { - (None, None) + let (mut resolve, mut main_world) = match wit_path { + [] => (None, None), + paths => { + let (resolve, world) = parse_wit(paths, world, features, all_features)?; + (Some(resolve), Some(world)) + } }; let import_interface_names = import_interface_names @@ -281,7 +282,8 @@ pub async fn componentize( .map(|(module, (config, world))| { Ok((module, match (world, config.config.wit_directory.as_deref()) { (_, Some(wit_path)) => { - let (my_resolve, mut world) = parse_wit(&config.path.join(wit_path), *world, features, all_features)?; + let paths = &[config.path.join(wit_path)]; + let (my_resolve, mut world) = parse_wit(paths, *world, features, all_features)?; if let Some(resolve) = &mut resolve { let remap = resolve.merge(my_resolve)?; @@ -303,13 +305,13 @@ pub async fn componentize( let resolve = if let Some(resolve) = resolve { resolve } else { - // If no WIT directory was provided as a parameter and none were referenced by Python packages, use ./wit - // by default. - let (my_resolve, world) = parse_wit(Path::new("wit"), world, features, all_features) - .context( - "no WIT files found; please specify the directory or file \ + // If no WIT directory was provided as a parameter and none were referenced by Python packages, use + // the default values. + let paths: &[&Path] = &[]; + let (my_resolve, world) = parse_wit(paths, world, features, all_features).context( + "no WIT files found; please specify the directory or file \ containing the WIT world you wish to target", - )?; + )?; main_world = Some(world); my_resolve }; @@ -556,11 +558,19 @@ pub async fn componentize( } fn parse_wit( - path: &Path, + paths: &[impl AsRef], world: Option<&str>, features: &[String], all_features: bool, ) -> Result<(Resolve, WorldId)> { + // If no WIT directory was provided as a parameter and none were referenced by Python packages, use ./wit + // by default. + if paths.is_empty() { + let paths = &[Path::new("wit")]; + return parse_wit(paths, world, features, all_features); + } + debug_assert!(!paths.is_empty(), "The paths should not be empty"); + let mut resolve = Resolve { all_features, ..Default::default() @@ -574,12 +584,19 @@ fn parse_wit( resolve.features.insert(feature.to_string()); } } - let pkg = if path.is_dir() { - resolve.push_dir(path)?.0 - } else { - let pkg = UnresolvedPackageGroup::parse_file(path)?; - resolve.push_group(pkg)? - }; + + let mut last_pkg = None; + for path in paths.iter().map(AsRef::as_ref) { + let pkg = if path.is_dir() { + resolve.push_dir(path)?.0 + } else { + let pkg = UnresolvedPackageGroup::parse_file(path)?; + resolve.push_group(pkg)? + }; + last_pkg = Some(pkg); + } + + let pkg = last_pkg.unwrap(); // The paths should not be empty let world = resolve.select_world(pkg, world)?; Ok((resolve, world)) } diff --git a/src/python.rs b/src/python.rs index bf97cb8..dad11b9 100644 --- a/src/python.rs +++ b/src/python.rs @@ -19,7 +19,7 @@ use { #[pyo3(name = "componentize")] #[pyo3(signature = (wit_path, world, features, all_features, world_module, python_path, module_worlds, app_name, output_path, stub_wasi, import_interface_names, export_interface_names))] fn python_componentize( - wit_path: Option, + wit_path: Vec, world: Option<&str>, features: Vec, all_features: bool, @@ -34,7 +34,7 @@ fn python_componentize( ) -> PyResult<()> { (|| { Runtime::new()?.block_on(crate::componentize( - wit_path.as_deref(), + &wit_path, world, &features, all_features, @@ -66,7 +66,7 @@ fn python_componentize( #[pyo3(name = "generate_bindings")] #[pyo3(signature = (wit_path, world, features, all_features, world_module, output_dir, import_interface_names, export_interface_names))] fn python_generate_bindings( - wit_path: PathBuf, + wit_path: Vec, world: Option<&str>, features: Vec, all_features: bool, diff --git a/src/test.rs b/src/test.rs index 66bc8c2..efd3a85 100644 --- a/src/test.rs +++ b/src/test.rs @@ -61,7 +61,7 @@ async fn make_component( } crate::componentize( - Some(&tempdir.path().join("app.wit")), + &[tempdir.path().join("app.wit")], None, &[], false,