Skip to content

Commit 2078752

Browse files
authored
add support for targeting multiple worlds with a single component (#76)
A while back, I added support for `componentize-py.toml` files in Python packages, which allows developers to embed WIT files in their Python packages and also specify where the bindings should be generated with respect to the Python module hierarchy. This is useful when publishing SDK packages for a WIT world, which may include additional, handwritten code on top of the generated bindings. However, it wasn't possible to use more than one such package at a time, nor was it possible to use such a package and _also_ target an additional, application-level WIT world beyond those defined in the package. This commit removes those instructions, allowing the user to specify multiple worlds (up to one per package, plus one additional, application-level world) when generating a component. Since those worlds might overlap, we merge them together using `wit_parser::Resolve::merge` and then generate bindings for each world such that the bindings for a given world may alias bindings generated for an earlier world. This required quite a bit of refactoring, and the code responsible for juggling all the WIT directories, worlds, and bindings is a bit awkward since it's trying to handle a wide range of use cases. It also needs more (automated) testing. In short, it's not fully baked yet, but I want to get a pre-release out so folks can try it while I clean things up. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 4fe52c3 commit 2078752

13 files changed

Lines changed: 811 additions & 479 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "componentize-py"
3-
version = "0.11.3"
3+
version = "0.12.0-rc1"
44
edition = "2021"
55
exclude = ["cpython"]
66

examples/cli/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-cli] `command` world.
1010
## Prerequisites
1111

1212
* `Wasmtime` 17.0.0 or later
13-
* `componentize-py` 0.11.3
13+
* `componentize-py` 0.12.0rc1
1414

1515
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1616
you don't have `cargo`, you can download and install from
1717
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
1818

1919
```
2020
cargo install --version 17.0.0 wasmtime-cli
21-
pip install componentize-py==0.11.3
21+
pip install componentize-py==0.12.0rc1
2222
```
2323

2424
## Running the demo

examples/http/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-http] `proxy` world.
1010
## Prerequisites
1111

1212
* `Wasmtime` 17.0.0 or later
13-
* `componentize-py` 0.11.3
13+
* `componentize-py` 0.12.0rc1
1414

1515
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1616
you don't have `cargo`, you can download and install from
1717
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
1818

1919
```
2020
cargo install --version 17.0.0 wasmtime-cli
21-
pip install componentize-py==0.11.3
21+
pip install componentize-py==0.12.0rc1
2222
```
2323

2424
## Running the demo

examples/matrix-math/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ within a guest component.
1111
## Prerequisites
1212

1313
* `wasmtime` 17.0.0 or later
14-
* `componentize-py` 0.11.3
14+
* `componentize-py` 0.12.0rc1
1515
* `NumPy`, built for WASI
1616

1717
Note that we use an unofficial build of NumPy since the upstream project does
@@ -23,7 +23,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
2323

2424
```
2525
cargo install --version 17.0.0 wasmtime-cli
26-
pip install componentize-py==0.11.3
26+
pip install componentize-py==0.12.0rc1
2727
curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz
2828
tar xf numpy-wasi.tar.gz
2929
```

examples/tcp/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ making an outbound TCP request using `wasi-sockets`.
1111
## Prerequisites
1212

1313
* `Wasmtime` 17.0.0 or later
14-
* `componentize-py` 0.11.3
14+
* `componentize-py` 0.12.0rc1
1515

1616
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1717
you don't have `cargo`, you can download and install from
1818
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
1919

2020
```
2121
cargo install --version 17.0.0 wasmtime-cli
22-
pip install componentize-py==0.11.3
22+
pip install componentize-py==0.12.0rc1
2323
```
2424

2525
## Running the demo

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ features = ["pyo3/extension-module"]
77

88
[project]
99
name = "componentize-py"
10-
version = "0.11.3"
10+
version = "0.12.0rc1"
1111
description = "Tool to package Python applications as WebAssembly components"
1212
readme = "README.md"
1313
license = { file = "LICENSE" }

src/bindings.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ struct MemInfo {
2828
table_alignment: u32,
2929
}
3030

31-
pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Result<Vec<u8>> {
31+
pub fn make_bindings(
32+
resolve: &Resolve,
33+
worlds: &IndexSet<WorldId>,
34+
summary: &Summary,
35+
) -> Result<Vec<u8>> {
3236
// TODO: deduplicate types
3337
let mut types = TypeSection::new();
3438
let mut imports = ImportSection::new();
@@ -363,21 +367,21 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re
363367
name: Cow::Borrowed("name"),
364368
data: Cow::Borrowed(&names_data),
365369
});
366-
result.section(&CustomSection {
367-
name: Cow::Owned(format!("component-type:{}", resolve.worlds[world].name)),
368-
data: Cow::Owned(metadata::encode(
369-
resolve,
370-
world,
371-
wit_component::StringEncoding::UTF8,
372-
None,
373-
)?),
374-
});
370+
for &world in worlds {
371+
result.section(&CustomSection {
372+
name: Cow::Owned(format!("component-type:{}", resolve.worlds[world].name)),
373+
data: Cow::Owned(metadata::encode(
374+
resolve,
375+
world,
376+
wit_component::StringEncoding::UTF8,
377+
None,
378+
)?),
379+
});
380+
}
375381

376382
let result = result.finish();
377383

378384
wasmparser::validate(&result)?;
379385

380-
std::fs::write("/tmp/bindings.so", &result)?;
381-
382386
Ok(result)
383387
}

src/command.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ pub struct Componentize {
6161
#[arg(short = 'p', long, default_value = ".")]
6262
pub python_path: Vec<String>,
6363

64+
/// Specify which world to use with which Python module. May be specified more than once.
65+
///
66+
/// Some Python modules (e.g. SDK wrappers around WIT APIs) may contain `componentize-py.toml` files which
67+
/// point to embedded WIT files, and those may define multiple WIT worlds. In this case, it may be necessary
68+
/// to specify which world on a module-by-module basis.
69+
///
70+
/// Note that these must be specified in topological order (i.e. if a module containing WIT files depends on
71+
/// other modules containing WIT files, it must be listed after all its dependencies).
72+
#[arg(short = 'm', long, value_parser = parse_module_world)]
73+
pub module_worlds: Vec<(String, String)>,
74+
6475
/// Output file to which to write the resulting component
6576
#[arg(short = 'o', long, default_value = "index.wasm")]
6677
pub output: PathBuf,
@@ -80,6 +91,13 @@ pub struct Bindings {
8091
pub world_module: Option<String>,
8192
}
8293

94+
fn parse_module_world(s: &str) -> Result<(String, String), String> {
95+
let (k, v) = s
96+
.split_once('=')
97+
.ok_or_else(|| format!("expected string of form `<key>=<value>`; got `{s}`"))?;
98+
Ok((k.to_string(), v.to_string()))
99+
}
100+
83101
pub fn run<T: Into<OsString> + Clone, I: IntoIterator<Item = T>>(args: I) -> Result<()> {
84102
let options = Options::parse_from(args);
85103
match options.command {
@@ -115,6 +133,11 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> {
115133
common.wit_path.as_deref(),
116134
common.world.as_deref(),
117135
&python_path.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
136+
&componentize
137+
.module_worlds
138+
.iter()
139+
.map(|(k, v)| (k.as_str(), v.as_str()))
140+
.collect::<Vec<_>>(),
118141
&componentize.app_name,
119142
&componentize.output,
120143
None,

0 commit comments

Comments
 (0)