Skip to content
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
1,201 changes: 1,136 additions & 65 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pyo3 = { version = "0.28.3", features = [
], optional = true }
wasmtime = "48.0.0"
wasmtime-wasi = { version = "48.0.0", features = [ "p3" ] }
wasm-pkg-client = "0.16.1"
once_cell = "1.20.2"
component-init-transform = "0.2.0"
async-trait = "0.1.83"
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ And finally generate the component:
componentize-py -d hello.wit -w hello componentize --stub-wasi app -o app.wasm
```

An exact, versioned world can also be loaded from a WebAssembly package
registry:

```shell
componentize-py -w wasi:cli/command@0.2.0 bindings wasi_cli
```

Use `--registry-config PATH` to merge a standard `wasm-pkg` configuration file
with the global configuration. Use `--no-default-registries` to start with an
empty configuration before loading that file. Registry loading is lazy, and a
matching local WIT definition takes precedence. Remote world references must
use the exact `namespace:package/world@version` form.

To test it, you can install `wasmtime-py` and write a simple host app which uses
it to load and run our component:

Expand Down
32 changes: 29 additions & 3 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ pub struct Common {
/// If this is not specified, the module name will default to "wit".
#[arg(long)]
pub bindings_module: Option<String>,

/// Path to a wasm package registry configuration file.
#[arg(long)]
pub registry_config: Option<PathBuf>,

/// Disable the global and built-in registry mappings.
#[arg(long)]
pub no_default_registries: bool,
}

#[derive(clap::Subcommand, Debug)]
Expand Down Expand Up @@ -242,7 +250,7 @@ pub fn run<T: Into<OsString> + Clone, I: IntoIterator<Item = T>>(args: I) -> Res
}

fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> {
BindingsGenerator {
let generator = BindingsGenerator {
wit_paths: &common
.wit_path
.iter()
Expand All @@ -268,8 +276,10 @@ fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> {
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
}
.generate()
registry_config: common.registry_config.as_deref(),
default_registries: !common.no_default_registries,
};
Runtime::new()?.block_on(generator.generate())
}

fn componentize(common: Common, componentize: Componentize) -> Result<()> {
Expand Down Expand Up @@ -323,6 +333,8 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> {
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
intersect_world: componentize.intersect_world.as_deref(),
registry_config: common.registry_config.as_deref(),
default_registries: !common.no_default_registries,
}
.generate(),
)?;
Expand Down Expand Up @@ -461,6 +473,8 @@ mod tests {
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
};
let bindings = bindings(out_dir.path());
generate_bindings(common, bindings)?;
Expand Down Expand Up @@ -489,6 +503,8 @@ mod tests {
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
};
let bindings = bindings(out_dir.path());
generate_bindings(common, bindings)?;
Expand Down Expand Up @@ -517,6 +533,8 @@ mod tests {
all_features: true,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
};
let bindings = bindings(out_dir.path());
generate_bindings(common, bindings)?;
Expand Down Expand Up @@ -544,6 +562,8 @@ mod tests {
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
};
let bindings = bindings(out_dir.path());
generate_bindings(common.clone(), bindings)?;
Expand Down Expand Up @@ -654,6 +674,8 @@ world lib-world {
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
},
bindings(lib_wit_dir),
)?;
Expand All @@ -668,6 +690,8 @@ world lib-world {
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
},
Componentize {
app_name: "app".into(),
Expand Down Expand Up @@ -702,6 +726,8 @@ world lib-world {
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
registry_config: None,
no_default_registries: false,
}
}

Expand Down
78 changes: 59 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod link;
mod prelink;
#[cfg(feature = "pyo3")]
mod python;
mod registry;
mod stubwasi;
mod summary;
#[cfg(test)]
Expand Down Expand Up @@ -183,12 +184,14 @@ pub struct BindingsGenerator<'a> {
pub output_dir: &'a Path,
pub import_interface_names: &'a HashMap<&'a str, &'a str>,
pub export_interface_names: &'a HashMap<&'a str, &'a str>,
pub registry_config: Option<&'a Path>,
pub default_registries: bool,
/// Write into `output_dir` even if the bindings module directory exists.
pub allow_existing: bool,
}

impl BindingsGenerator<'_> {
pub fn generate(&self) -> Result<()> {
pub async fn generate(&self) -> Result<()> {
// Here we parse the specified WIT paths and resolve the specified
// worlds, then union them together and generate stub code for the
// unioned world.
Expand All @@ -215,12 +218,27 @@ impl BindingsGenerator<'_> {
packages.push((path, resolve.push_path(path)?.0));
}

if packages.is_empty() {
if packages.is_empty()
&& (Path::new("wit").exists()
|| !self
.worlds
.iter()
.any(|world| registry::is_qualified_world_reference(world)))
{
// If no WIT directory was provided as a parameter, use ./wit by default.
packages.push((Path::new("wit"), resolve.push_path("wit")?.0));
}

let worlds = select_worlds(&resolve, self.worlds, &packages)?;
let worlds = registry::resolve_requested_worlds(
&mut resolve,
&packages,
self.worlds,
self.registry_config,
self.default_registries,
)
.await?
.into_iter()
.collect::<IndexSet<_>>();
let world = match &worlds.iter().copied().collect::<Vec<_>>()[..] {
[] => select_world(&resolve, None, &packages)?,
&[world] => world,
Expand Down Expand Up @@ -395,6 +413,8 @@ pub struct ComponentGenerator<'a> {
pub import_interface_names: &'a HashMap<&'a str, &'a str>,
pub export_interface_names: &'a HashMap<&'a str, &'a str>,
pub intersect_world: Option<&'a str>,
pub registry_config: Option<&'a Path>,
pub default_registries: bool,
}

impl ComponentGenerator<'_> {
Expand Down Expand Up @@ -550,16 +570,47 @@ impl ComponentGenerator<'_> {
packages.push((path, resolve.push_path(path)?.0));
}

if packages.is_empty() {
let registry_worlds = self
.worlds
.iter()
.copied()
.chain(self.intersect_world)
.collect::<Vec<_>>();

if packages.is_empty()
&& (Path::new("wit").exists()
|| !registry_worlds
.iter()
.any(|world| registry::is_qualified_world_reference(world)))
{
// If no WIT directory was provided as a parameter and none were
// referenced by Python packages, use ./wit by default.
packages.push((Path::new("wit"), resolve.push_path("wit")?.0));
}

let worlds = select_worlds(&resolve, self.worlds, &packages)?;
let default_world =
if self.worlds.is_empty() && configs.values().all(|(_, worlds)| worlds.is_empty()) {
Some(select_world(&resolve, None, &packages)?)
} else {
None
};

let registry_world_ids = registry::resolve_requested_worlds(
&mut resolve,
&packages,
&registry_worlds,
self.registry_config,
self.default_registries,
)
.await?;

let worlds = registry_world_ids[..self.worlds.len()]
.iter()
.copied()
.collect::<IndexSet<_>>();

let intersector = if let Some(world) = self.intersect_world {
let intersector = select_world(&resolve, Some(world), &packages)?;
let intersector = if self.intersect_world.is_some() {
let intersector = *registry_world_ids.last().unwrap();

for &world in &worlds {
intersect_world(&mut resolve, intersector, world);
Expand Down Expand Up @@ -588,7 +639,7 @@ impl ComponentGenerator<'_> {

if all_worlds.is_empty() {
// No worlds specified; pick the default one, if available:
let world = select_world(&resolve, None, &packages)?;
let world = default_world.expect("the local default world was selected above");

if let Some(intersector) = intersector {
intersect_world(&mut resolve, intersector, world);
Expand Down Expand Up @@ -1036,17 +1087,6 @@ fn select_world(
})
}

fn select_worlds(
resolve: &Resolve,
worlds: &[&str],
packages: &[(&Path, PackageId)],
) -> Result<IndexSet<WorldId>> {
worlds
.iter()
.map(|world| select_world(resolve, Some(world), packages))
.collect()
}

fn union_world(
resolve: &mut Resolve,
name: &str,
Expand Down
52 changes: 32 additions & 20 deletions src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use {
#[allow(clippy::too_many_arguments)]
#[pyo3::pyfunction]
#[pyo3(name = "componentize")]
#[pyo3(signature = (wit_path, worlds, features, all_features, bindings_module, python_path, module_worlds, app_name, output_path, stub_wasi, import_interface_names, export_interface_names, full_names = None, intersect_world = None, world_module = None))]
#[pyo3(signature = (wit_path, worlds, features, all_features, bindings_module, python_path, module_worlds, app_name, output_path, stub_wasi, import_interface_names, export_interface_names, full_names = None, intersect_world = None, world_module = None, registry_config = None, no_default_registries = false))]
fn python_componentize(
wit_path: Vec<PathBuf>,
worlds: Vec<String>,
Expand All @@ -35,6 +35,8 @@ fn python_componentize(
full_names: Option<bool>,
intersect_world: Option<&str>,
world_module: Option<&str>,
registry_config: Option<PathBuf>,
no_default_registries: bool,
) -> PyResult<()> {
let bindings_module = crate::resolve_deprecated(
bindings_module.map(str::to_owned),
Expand Down Expand Up @@ -72,6 +74,8 @@ fn python_componentize(
.map(|(a, b)| (a.as_ref(), b.as_ref()))
.collect(),
intersect_world,
registry_config: registry_config.as_deref(),
default_registries: !no_default_registries,
}
.generate(),
)
Expand All @@ -82,7 +86,7 @@ fn python_componentize(
#[allow(clippy::too_many_arguments)]
#[pyo3::pyfunction]
#[pyo3(name = "generate_bindings")]
#[pyo3(signature = (wit_path, worlds, features, all_features, bindings_module, output_dir, import_interface_names, export_interface_names, allow_existing = false, full_names = None, world_module = None))]
#[pyo3(signature = (wit_path, worlds, features, all_features, bindings_module, output_dir, import_interface_names, export_interface_names, allow_existing = false, full_names = None, world_module = None, registry_config = None, no_default_registries = false))]
fn python_generate_bindings(
wit_path: Vec<PathBuf>,
worlds: Vec<String>,
Expand All @@ -95,6 +99,8 @@ fn python_generate_bindings(
allow_existing: bool,
full_names: Option<bool>,
world_module: Option<&str>,
registry_config: Option<PathBuf>,
no_default_registries: bool,
) -> PyResult<()> {
let bindings_module = crate::resolve_deprecated(
bindings_module.map(str::to_owned),
Expand All @@ -103,24 +109,30 @@ fn python_generate_bindings(
false,
)
.map_err(|e| PyAssertionError::new_err(format!("{e:?}")))?;
BindingsGenerator {
wit_paths: &wit_path.iter().map(|v| v.as_path()).collect::<Vec<_>>(),
worlds: &worlds.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
features: &features.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
all_features,
bindings_module: bindings_module.as_deref(),
output_dir: &output_dir,
allow_existing,
import_interface_names: &import_interface_names
.iter()
.map(|(a, b)| (a.as_ref(), b.as_ref()))
.collect(),
export_interface_names: &export_interface_names
.iter()
.map(|(a, b)| (a.as_ref(), b.as_ref()))
.collect(),
}
.generate()
(|| {
Runtime::new()?.block_on(
BindingsGenerator {
wit_paths: &wit_path.iter().map(|v| v.as_path()).collect::<Vec<_>>(),
worlds: &worlds.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
features: &features.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
all_features,
bindings_module: bindings_module.as_deref(),
output_dir: &output_dir,
allow_existing,
import_interface_names: &import_interface_names
.iter()
.map(|(a, b)| (a.as_ref(), b.as_ref()))
.collect(),
export_interface_names: &export_interface_names
.iter()
.map(|(a, b)| (a.as_ref(), b.as_ref()))
.collect(),
registry_config: registry_config.as_deref(),
default_registries: !no_default_registries,
}
.generate(),
)
})()
.map_err(|e| PyAssertionError::new_err(format!("{e:?}")))
}

Expand Down
Loading
Loading