Skip to content

Commit

Permalink
Merge pull request #154 from kate-goldenring/wac-plug
Browse files Browse the repository at this point in the history
fix(composition): replace deprecated `wasm-tools compose` with `wac`
  • Loading branch information
kate-goldenring authored Sep 24, 2024
2 parents a262609 + d058f6d commit 120119d
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 27 deletions.
67 changes: 61 additions & 6 deletions component-model/examples/tutorial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ world adder {
}
```


```wit calculator
package docs:[email protected];
Expand All @@ -44,18 +43,74 @@ To expand the exercise to add more components, add another operator world, expan

## Building and running the example

To compose a calculator component with an add operator, run the following:
Use [`cargo-component`](https://github.com/bytecodealliance/cargo-component) and [`wac`](https://github.com/bytecodealliance/wac) to build and compose the calculator component.

```sh
(cd calculator && cargo component build --release)
(cd adder && cargo component build --release)
(cd command && cargo component build --release)
cd ..
wasm-tools compose calculator/target/wasm32-wasi/release/calculator.wasm -d adder/target/wasm32-wasi/release/adder.wasm -o composed.wasm
wasm-tools compose command/target/wasm32-wasi/release/command.wasm -d composed.wasm -o final.wasm
wac plug calculator/target/wasm32-wasi/release/calculator.wasm --plug adder/target/wasm32-wasi/release/adder.wasm -o composed.wasm
wac plug command/target/wasm32-wasi/release/command.wasm --plug composed.wasm -o final.wasm
```

Now, run the component with Wasmtime:

```sh
wasmtime run final.wasm 1 2 add
1 + 2 = 3
```

## Composing with the WAC Language

`wac plug` is a convenience to achieve a common pattern in component compositions like above. However, composition can be arbitrarily complicated. In cases where `wac plug` is not sufficient, the WAC language can give us the ability to create arbitrarily complex compositions. To get more experience using the WAC language, let's look at how we could use it to create our composition.

`wac` can compose local components and components hosted in registries. To compose local components, first move the components to a `deps` folder, the default location in which `wac` looks for local components. `wac` infers the subpath to components from the package name of components defined in a WAC file. For example, if the instantiation expression for the adder component in the WAC file is `new docs:adder-impl{}`, the local component is expected to have the following path `deps/docs/adder-impl.wasm`. With this in mind, let's move all out components to a `deps/docs` folder and rename to ease clarifying WAC concepts.

```sh
mkdir -p deps/docs
cp adder/target/wasm32-wasi/release/adder.wasm deps/docs/adder-impl.wasm
cp calculator/target/wasm32-wasi/release/calculator.wasm deps/docs/calculator-impl.wasm
cp command/target/wasm32-wasi/release/command.wasm deps/docs/command-impl.wasm
```

Now, run the component with wasmtime:
Now we are ready to construct a WAC file to define our composition. Ours instantiates our three components, declaring
which components satisfy each of their imports. It ends with an export of the `wasi:cli/run` interface from the command component. This is the export that the Wasmtime CLI requires in order to execute the final component on the command line.

```wac
// Provide a package name for the resulting composition
package example:composition;
// Instantiate the adder-impl component that implements the adder world.
// We are giving this instance the local name `adder-instance`.
let adder-instance = new docs:adder-impl { };
// Instantiate the calculator-impl component that implements the calculator world.
// In the `new` expression, specify the source of the `add` import to be `adder-instance`'s `add` export.
let calculator-instance = new docs:calculator-impl { add: adder-instance.add };
// Instantiate a command-impl component that implements the app world.
// The command component might import other interfaces, such as WASI interfaces, but we want to leave
// those as imports in the final component, so supply `...` to allow those other imports to remain unresolved.
// The command's exports (in this case, `wasi:cli/run`) remain unaffected in the resulting instance.
let command-instance = new docs:command-impl { calculate: calculator-instance.calculate,... };
// Export the `wasi:cli/run` interface from the command instance
// This could also have been expressed using the postfix access expression `command-instance.run`
export command-instance["wasi:cli/[email protected]"];
```

Now, perform your composition by passing the WAC file to `wac compose`.

```sh
wac compose composition.wac -o final.wasm
```

> Note, instead of moving all the components to a `deps/docs` directory, you can pass the paths to the components inline
> ```sh
> wac compose --dep docs:adder-impl=./adder/target/wasm32-wasi/release/adder.wasm --dep docs:calculator-impl=./calculator/target/wasm32-wasi/release/calculator.wasm --dep docs:command-impl=./command/target/wasm32-wasi/release/command.wasm -o final.wasm composition.wac
> ```
Run the component with Wasmtime:
```sh
wasmtime run final.wasm 1 2 add
Expand Down
8 changes: 4 additions & 4 deletions component-model/examples/tutorial/adder/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
// Options used:
#[allow(dead_code)]
pub mod exports {
Expand Down Expand Up @@ -149,14 +149,14 @@ macro_rules! __export_adder_impl {
pub(crate) use __export_adder_impl as export;

#[cfg(target_arch = "wasm32")]
#[link_section = "component-type:wit-bindgen:0.24.0:adder:encoded world"]
#[link_section = "component-type:wit-bindgen:0.25.0:adder:encoded world"]
#[doc(hidden)]
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 203] = *b"\
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07P\x01A\x02\x01A\x02\x01\
B\x02\x01@\x02\x01ay\x01by\0y\x04\0\x03add\x01\0\x04\x01\x14docs:adder/[email protected]\
\x05\0\x04\x01\x16docs:adder/[email protected]\x04\0\x0b\x0b\x01\0\x05adder\x03\0\0\0G\
\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.202.0\x10wit-bindgen\
-rust\x060.24.0";
\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.208.1\x10wit-bindgen\
-rust\x060.25.0";

#[inline(never)]
#[doc(hidden)]
Expand Down
9 changes: 5 additions & 4 deletions component-model/examples/tutorial/calculator/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
// Options used:
#[allow(dead_code)]
pub mod docs {
Expand Down Expand Up @@ -61,7 +61,8 @@ pub mod exports {
}

impl Op {
pub(crate) unsafe fn _lift(val: u8) -> Op {
#[doc(hidden)]
pub unsafe fn _lift(val: u8) -> Op {
if !cfg!(debug_assertions) {
return ::core::mem::transmute(val);
}
Expand Down Expand Up @@ -214,7 +215,7 @@ macro_rules! __export_calculator_impl {
pub(crate) use __export_calculator_impl as export;

#[cfg(target_arch = "wasm32")]
#[link_section = "component-type:wit-bindgen:0.24.0:calculator:encoded world"]
#[link_section = "component-type:wit-bindgen:0.25.0:calculator:encoded world"]
#[doc(hidden)]
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 308] = *b"\
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xb3\x01\x01A\x02\x01\
Expand All @@ -223,7 +224,7 @@ [email protected]\x05\0\x01B\x04\x01m\x01\x03add\x04\0\x02op\x03\0\0\x01@\x03\x02op\x01\
xy\x01yy\0y\x04\0\x0feval-expression\x01\x02\x04\x01\x1fdocs:calculator/calculat\
[email protected]\x05\x01\x04\x01\x20docs:calculator/[email protected]\x04\0\x0b\x10\x01\0\x0a\
calculator\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070\
.202.0\x10wit-bindgen-rust\x060.24.0";
.208.1\x10wit-bindgen-rust\x060.25.0";

#[inline(never)]
#[doc(hidden)]
Expand Down
11 changes: 6 additions & 5 deletions component-model/examples/tutorial/command/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
// Options used:
#[allow(dead_code)]
pub mod docs {
Expand Down Expand Up @@ -26,7 +26,8 @@ pub mod docs {
}

impl Op {
pub(crate) unsafe fn _lift(val: u8) -> Op {
#[doc(hidden)]
pub unsafe fn _lift(val: u8) -> Op {
if !cfg!(debug_assertions) {
return ::core::mem::transmute(val);
}
Expand Down Expand Up @@ -134,15 +135,15 @@ mod _rt {
}

#[cfg(target_arch = "wasm32")]
#[link_section = "component-type:wit-bindgen:0.24.0:app:encoded world"]
#[link_section = "component-type:wit-bindgen:0.25.0:app:encoded world"]
#[doc(hidden)]
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 246] = *b"\
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07}\x01A\x02\x01A\x02\x01\
B\x04\x01m\x01\x03add\x04\0\x02op\x03\0\0\x01@\x03\x02op\x01\x01xy\x01yy\0y\x04\0\
\x0feval-expression\x01\x02\x03\x01\x1fdocs:calculator/[email protected]\x05\0\x04\
\x01\x19docs:calculator/[email protected]\x04\0\x0b\x09\x01\0\x03app\x03\0\0\0G\x09produ\
cers\x01\x0cprocessed-by\x02\x0dwit-component\x070.202.0\x10wit-bindgen-rust\x06\
0.24.0";
cers\x01\x0cprocessed-by\x02\x0dwit-component\x070.208.1\x10wit-bindgen-rust\x06\
0.25.0";

#[inline(never)]
#[doc(hidden)]
Expand Down
20 changes: 20 additions & 0 deletions component-model/examples/tutorial/composition.wac
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Provide a package name for the resulting composition
package example:composition;

// Instantiate the adder-impl component that implements the adder world.
// Bind this instance's exports to the local name `adder-instance`.
let adder-instance = new docs:adder-impl { };

// Instantiate the calculator-impl component that implements the calculator world.
// In the `new` expression, connect it's `add` import to the `adder-instance`'s `add` export.
let calculator-instance = new docs:calculator-impl { add: adder-instance.add };

// Instantiate a command-impl component that implements the app world.
// The command component might import other interfaces, such as WASI interfaces, but we want to leave
// those as imports in the final component, so supply `...` to allow those other imports to remain unresolved.
// The command's exports (in this case, `wasi:cli/run`) remain unaffected in the resulting instance.
let command-instance = new docs:command-impl { calculate: calculator-instance.calculate,... };

// Export the `wasi:cli/run` interface from the command instance
// This could also have been expressed using the postfix access expression `command-instance.run`
export command-instance["wasi:cli/[email protected]"];
51 changes: 43 additions & 8 deletions component-model/src/creating-and-consuming/composing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@ interface validator {
validate-text: func(text: string) -> string;
}
world {
world validator {
export validator;
import docs:regex/[email protected];
}
```

```wit
// component 'regex'
package docs:[email protected];
interface match {
first-match: func(regex: string, text: string) -> string;
}
world {
world regex {
export match;
}
```
Expand All @@ -49,21 +51,54 @@ Component composition tools are in their early stages right now. Here are some
* Composition is asymmetrical. It is not just "gluing components together" - it takes a primary component which has imports, and satisfies its imports using dependency components. For example, composing an implementation of `validator` with an implementation of `regex` makes sense because `validator` has a dependency that `regex` can satisfy; doing it the other way round doesn't work, because `regex` doesn't have any dependencies, let alone ones that `validator` can satisfy.
* Composition cares about interface versions, and current tools are inconsistent about when they infer or inject versions. For example, if a Rust component exports `test:mypackage`, `cargo component build` will decorate this with the crate version, e.g. `test:[email protected]`. If another Rust component _imports_ an interface from `test:mypackage`, that won't match `test:[email protected]`. You can use [`wasm-tools component wit`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) to view the imports and exports embedded in the `.wasm` files and check whether they match up.

## Composing components with `wasm-tools`
## Composing components with WAC

You can use the [WAC](https://github.com/bytecodealliance/wac) CLI to compose components at the command line.

The [`wasm-tools` suite](https://github.com/bytecodealliance/wasm-tools) includes a `compose` command which can be used to compose components at the command line.
To perform quick and simple compositions, use the `wac plug` command. `wac plug` satisfies the import of a "socket" component by plugging a "plug" component's export into the socket. For example, a component that implements the [`validator` world above](#what-is-composition) needs to satisfy it's `match` import. It is a socket. While a component that implements the `regex` world, exports the `match` interface, and can be used as a plug. `wac plug` can plug a regex component's export into the validator component's import, creating a resultant composition:

```sh
wac plug validator-component.wasm --plug regex-component.wasm -o composed.wasm
```

To compose a component with the components it directly depends on, run:
A component can also be composed with two components it depends on.

```sh
wasm-tools compose path/to/component.wasm -d path/to/dep1.wasm -d path/to/dep2.wasm -o composed.wasm
wac plug path/to/component.wasm --plug path/to/dep1.wasm --plug path/to/dep2.wasm -o composed.wasm
```

Here `component.wasm` is the component that imports interfaces from `dep1.wasm` and `dep2.wasm`, which export them. The composed component, with those dependencies satisfied and tucked away inside it, is saved to `composed.wasm`.

> This syntax doesn't cover transitive dependencies. If, for example, `dep1.wasm` has unsatisfied imports that you want to satisfy from `dep3.wasm`, you'll need to use a [configuration file](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wasm-compose/CONFIG.md). (Or you can compose `dep1.wasm` with `dep3.wasm` first, then refer to that composed component instead of `dep1.wasm`. This doesn't scale to lots of transitive dependencies though!)
The `plug` syntax doesn't cover transitive dependencies. If, for example, `dep1.wasm` has unsatisfied imports that you want to satisfy from `dep3.wasm`, you'd need to be deliberate about the order of your composition. You could compose `dep1.wasm` with `dep3.wasm` first, then refer to that composed component instead of `dep1.wasm`. However, this doesn't scale to lots of transitive dependencies, which is why the WAC language was created.

### Advanced composition with the WAC language

`wac plug` is a convenience to achieve a common pattern in component compositions like above. However, composition can be arbitrarily complicated. In cases where `wac plug` is not sufficient, the [WAC language](https://github.com/bytecodealliance/wac/blob/main/LANGUAGE.md) can give us the ability to create arbitrarily complex compositions.

In a WAC file, you use the WAC language to describe a composition. For example, the following is a WAC file that could be used to create that validator component from [earlier](#what-is-composition).

```
//composition.wac
// Provide a package name for the resulting composition
package docs:composition;
// Instantiate the regex-impl component that implements the `regex` world. Bind this instance's exports to the local name `regex`.
let regex = new docs:regex-impl { };
// Instantiate the validator-impl component which implements the `validator` world and imports the match interface from the regex component.
let validator = new docs:validator-impl { match: regex.match, ... };
// Export all remaining exports of the validator instance
export validator...;
```

Then, `wac compose` can be used to compose the components, passing in the paths to the components. Alternatively, you can place the components in a `deps` directory with an expected structure, and in the near future, you will be able to pull in components from registries. See the [`wac` documentation](https://github.com/bytecodealliance/wac) for more details.

```sh
wac compose --dep docs:regex-impl=regex-component.wasm --dep docs:validator-impl=validator-component.wasm -o composed.wasm composition.wac
```

For full information about `wasm-tools compose` including how to configure more advanced scenarios, see [the `wasm-tools compose` documentation](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-compose).
For an in depth description about how to use the wac tool, you can check out the [wac language index](https://github.com/bytecodealliance/wac/blob/main/LANGUAGE.md) and [examples](https://github.com/bytecodealliance/wac/tree/main/examples).

## Composing components with a visual interface

Expand Down

0 comments on commit 120119d

Please sign in to comment.