Skip to content

Commit db41f3e

Browse files
authored
add test for multiworld scenario (#77)
* add test for multiworld scenario Signed-off-by: Joel Dice <joel.dice@fermyon.com> * bump version to 0.12.0 Signed-off-by: Joel Dice <joel.dice@fermyon.com> --------- Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 2078752 commit db41f3e

21 files changed

Lines changed: 267 additions & 71 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.12.0-rc1"
3+
version = "0.12.0"
44
edition = "2021"
55
exclude = ["cpython"]
66

examples/cli/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,23 @@ run a Python-based component targetting the [wasi-cli] `command` world.
99

1010
## Prerequisites
1111

12-
* `Wasmtime` 17.0.0 or later
13-
* `componentize-py` 0.12.0rc1
12+
* `Wasmtime` 18.0.0 or later
13+
* `componentize-py` 0.12.0
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
17-
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
17+
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
1818

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

2424
## Running the demo
2525

2626
```
2727
componentize-py -d ../../wit -w wasi:cli/command@0.2.0 componentize app -o cli.wasm
28-
wasmtime run --wasm component-model cli.wasm
28+
wasmtime run cli.wasm
2929
```
3030

3131
The `wasmtime run` command above should print "Hello, world!".

examples/http/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ run a Python-based component targetting the [wasi-http] `proxy` world.
99

1010
## Prerequisites
1111

12-
* `Wasmtime` 17.0.0 or later
13-
* `componentize-py` 0.12.0rc1
12+
* `Wasmtime` 18.0.0 or later
13+
* `componentize-py` 0.12.0
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
17-
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
17+
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
1818

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

2424
## Running the demo

examples/matrix-math/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ within a guest component.
1010

1111
## Prerequisites
1212

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

1717
Note that we use an unofficial build of NumPy since the upstream project does
1818
not yet publish WASI builds.
1919

2020
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
2121
you don't have `cargo`, you can download and install from
22-
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
22+
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
2323

2424
```
25-
cargo install --version 17.0.0 wasmtime-cli
26-
pip install componentize-py==0.12.0rc1
25+
cargo install --version 18.0.0 wasmtime-cli
26+
pip install componentize-py==0.12.0
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
```
@@ -32,7 +32,7 @@ tar xf numpy-wasi.tar.gz
3232

3333
```
3434
componentize-py -d ../../wit -w matrix-math componentize app -o matrix-math.wasm
35-
wasmtime run --wasm component-model matrix-math.wasm '[[1, 2], [4, 5], [6, 7]]' '[[1, 2, 3], [4, 5, 6]]'
35+
wasmtime run matrix-math.wasm '[[1, 2], [4, 5], [6, 7]]' '[[1, 2, 3], [4, 5, 6]]'
3636
```
3737

3838
The second command above should print the following:

examples/tcp/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ making an outbound TCP request using `wasi-sockets`.
1010

1111
## Prerequisites
1212

13-
* `Wasmtime` 17.0.0 or later
14-
* `componentize-py` 0.12.0rc1
13+
* `Wasmtime` 18.0.0 or later
14+
* `componentize-py` 0.12.0
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
18-
https://github.com/bytecodealliance/wasmtime/releases/tag/v17.0.0.
18+
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
1919

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

2525
## Running the demo
@@ -35,7 +35,7 @@ Now, build and run the example, using the same port you gave to `netcat`.
3535

3636
```
3737
componentize-py -d ../../wit -w wasi:cli/command@0.2.0 componentize app -o tcp.wasm
38-
wasmtime run --wasm component-model --wasi inherit-network tcp.wasm 127.0.0.1:3456
38+
wasmtime run --wasi inherit-network tcp.wasm 127.0.0.1:3456
3939
```
4040

4141
The program will open a TCP connection, send a message, and wait to receive a

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.12.0rc1"
10+
version = "0.12.0"
1111
description = "Tool to package Python applications as WebAssembly components"
1212
readme = "README.md"
1313
license = { file = "LICENSE" }

src/lib.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ pub async fn componentize(
214214
output_path: &Path,
215215
add_to_linker: Option<&dyn Fn(&mut Linker<Ctx>) -> Result<()>>,
216216
) -> Result<()> {
217+
// Untar the embedded copy of the Python standard library into a temporary directory
217218
let stdlib = tempfile::tempdir()?;
218219

219220
Archive::new(Decoder::new(Cursor::new(include_bytes!(concat!(
@@ -222,6 +223,7 @@ pub async fn componentize(
222223
))))?)
223224
.unpack(stdlib.path())?;
224225

226+
// Untar the embedded copy of helper utilties into a temporary directory
225227
let bundled = tempfile::tempdir()?;
226228

227229
Archive::new(Decoder::new(Cursor::new(include_bytes!(concat!(
@@ -230,6 +232,9 @@ pub async fn componentize(
230232
))))?)
231233
.unpack(bundled.path())?;
232234

235+
// Search `python_path` for native extension libraries and/or componentize-py.toml files. Packages containing
236+
// the latter may contain their own WIT files defining their own worlds (in addition to what the caller
237+
// specified as paramters), which we'll try to match up with `module_worlds` in the next step.
233238
let mut raw_configs = Vec::new();
234239
let mut library_path = Vec::with_capacity(python_path.len());
235240
for path in python_path {
@@ -244,6 +249,12 @@ pub async fn componentize(
244249
library_path.push((*path, libraries));
245250
}
246251

252+
// Validate the paths parsed from any componentize-py.toml files discovered above and match them up with
253+
// `module_worlds` entries. Note that we use an `IndexMap` to preserve the order specified in `module_worlds`,
254+
// which is required to be topologically sorted with respect to package dependencies.
255+
//
256+
// For any packages which contain componentize-py.toml files but no corresponding `module_worlds` entry, we use
257+
// the `world` parameter as a default.
247258
let configs = {
248259
let mut configs = raw_configs
249260
.into_iter()
@@ -279,6 +290,9 @@ pub async fn componentize(
279290
ordered
280291
};
281292

293+
// Next, iterate over all the WIT directories, merging them into a single `Resolve`, and matching Python
294+
// packages to `WorldId`s.
295+
282296
let (mut resolve, mut main_world) = if let Some(path) = wit_path {
283297
let (resolve, world) = parse_wit(path, world)?;
284298
(Some(resolve), Some(world))
@@ -313,11 +327,19 @@ pub async fn componentize(
313327
let resolve = if let Some(resolve) = resolve {
314328
resolve
315329
} else {
316-
let (my_resolve, world) = parse_wit(Path::new("wit"), world)?;
330+
// If no WIT directory was provided as a parameter and none were referenced by Python packages, use ./wit
331+
// by default.
332+
let (my_resolve, world) = parse_wit(Path::new("wit"), world).context(
333+
"no WIT files found; please specify the directory or file \
334+
containing the WIT world you wish to target",
335+
)?;
317336
main_world = Some(world);
318337
my_resolve
319338
};
320339

340+
// Extract relevant metadata from the `Resolve` into a `Summary` instance, which we'll use to generate Wasm-
341+
// and Python-level bindings.
342+
321343
let worlds = configs
322344
.values()
323345
.filter_map(|(_, world)| *world)
@@ -326,6 +348,7 @@ pub async fn componentize(
326348

327349
let summary = Summary::try_new(&resolve, &worlds)?;
328350

351+
// Link all the libraries (including any native extensions) into a single component.
329352
let mut linker = wit_component::Linker::default()
330353
.validate(true)
331354
.library(
@@ -427,6 +450,10 @@ pub async fn componentize(
427450

428451
let component = linker.encode()?;
429452

453+
// Pre-initialize the component by running it through `component_init::initialize`. Currently, this is the
454+
// application's first and only chance to load any standard or third-party modules since we do not yet include
455+
// a virtual filesystem in the component to make those modules available at runtime.
456+
430457
let stdout = MemoryOutputPipe::new(10000);
431458
let stderr = MemoryOutputPipe::new(10000);
432459

@@ -452,6 +479,7 @@ pub async fn componentize(
452479
"bundled",
453480
);
454481

482+
// Generate guest mounts for each host directory in `python_path`.
455483
for (index, path) in python_path.iter().enumerate() {
456484
wasi.preopened_dir(
457485
Dir::open_ambient_dir(path, cap_std::ambient_authority())
@@ -462,6 +490,9 @@ pub async fn componentize(
462490
);
463491
}
464492

493+
// For each Python package with a `componentize-py.toml` file that specifies where generated bindings for that
494+
// package should be placed, generate the bindings and place them as indicated.
495+
465496
let mut world_dir_mounts = Vec::new();
466497
let mut locations = Locations::default();
467498
let mut saw_main_world = false;
@@ -518,6 +549,7 @@ pub async fn componentize(
518549
));
519550
}
520551

552+
// If the caller specified a world and we haven't already generated bindings for it above, do so now.
521553
if let (Some(world), false) = (main_world, saw_main_world) {
522554
let module = resolve.worlds[world].name.to_snake_case();
523555
let world_dir = tempfile::tempdir()?;
@@ -539,8 +571,12 @@ pub async fn componentize(
539571
}
540572
}
541573

574+
// Generate a `Symbols` object containing metadata to be passed to the pre-init function. The runtime library
575+
// will use this to look up types and functions that will later be referenced by the generated Wasm code.
542576
let symbols = summary.collect_symbols(&locations);
543577

578+
// Finally, pre-initialize the component writing the result to `output_path`.
579+
544580
let python_path = (0..python_path.len())
545581
.map(|index| format!("/{index}"))
546582
.collect::<Vec<_>>()

src/summary.rs

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,21 +1463,10 @@ class {camel}(Protocol):
14631463
};
14641464

14651465
let aliases = if let (Some(code), false) = (code.as_ref(), names.is_empty()) {
1466-
let aliases = iter::once(format!(
1467-
"import {}",
1468-
if let Some((start, _)) = world_module.split_once('.') {
1469-
start
1470-
} else {
1471-
world_module
1472-
}
1473-
))
1474-
.chain(
1475-
names
1476-
.iter()
1477-
.map(|name| format!("{name} = {world_module}.{name}")),
1478-
)
1479-
.collect::<Vec<_>>()
1480-
.join("\n");
1466+
let aliases = iter::once(world_module_import(world_module, "peer"))
1467+
.chain(names.iter().map(|name| format!("{name} = peer.{name}")))
1468+
.collect::<Vec<_>>()
1469+
.join("\n");
14811470

14821471
Some(match code {
14831472
Code::Shared(_) => Code::Shared(aliases),
@@ -1711,21 +1700,13 @@ import weakref
17111700
{
17121701
let mut file = File::create(path.join("types.py"))?;
17131702
if let Some(module) = locations.types_module.as_ref() {
1714-
writeln!(
1715-
file,
1716-
"import {}",
1717-
if let Some((start, _)) = module.split_once('.') {
1718-
start
1719-
} else {
1720-
module
1721-
}
1722-
)?;
1703+
writeln!(file, "{}", world_module_import(module, "peer"))?;
17231704
write!(
17241705
file,
1725-
"Some = {module}.Some
1726-
Ok = {module}.types.Ok
1727-
Err = {module}.types.Err
1728-
Result = {module}.types.Result
1706+
"Some = peer.types.Some
1707+
Ok = peer.types.Ok
1708+
Err = peer.types.Err
1709+
Result = peer.types.Result
17291710
"
17301711
)?;
17311712
} else {
@@ -2162,3 +2143,11 @@ fn matches_resource(function: &MyFunction, resource: TypeId, direction: Directio
21622143
_ => false,
21632144
}
21642145
}
2146+
2147+
fn world_module_import(name: &str, alias: &str) -> String {
2148+
if let Some((front, rear)) = name.rsplit_once('.') {
2149+
format!("from {front} import {rear} as {alias}")
2150+
} else {
2151+
format!("import {name} as {alias}")
2152+
}
2153+
}

0 commit comments

Comments
 (0)