-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #154 from kate-goldenring/wac-plug
fix(composition): replace deprecated `wasm-tools compose` with `wac`
- Loading branch information
Showing
6 changed files
with
139 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ world adder { | |
} | ||
``` | ||
|
||
|
||
```wit calculator | ||
package docs:[email protected]; | ||
|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
|
@@ -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)] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
|
@@ -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); | ||
} | ||
|
@@ -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\ | ||
|
@@ -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)] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
|
@@ -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); | ||
} | ||
|
@@ -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)] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]"]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
} | ||
``` | ||
|
@@ -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 | ||
|
||
|