Skip to content

Commit d058f6d

Browse files
fix(composition): replace deprecated 'wasm-tools compose' with wac
Signed-off-by: Kate Goldenring <kate.goldenring@fermyon.com>
1 parent 09ad0a3 commit d058f6d

6 files changed

Lines changed: 139 additions & 27 deletions

File tree

component-model/examples/tutorial/README.md

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ world adder {
1818
}
1919
```
2020

21-
2221
```wit calculator
2322
package docs:calculator@0.1.0;
2423
@@ -44,18 +43,74 @@ To expand the exercise to add more components, add another operator world, expan
4443

4544
## Building and running the example
4645

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

4948
```sh
5049
(cd calculator && cargo component build --release)
5150
(cd adder && cargo component build --release)
5251
(cd command && cargo component build --release)
53-
cd ..
54-
wasm-tools compose calculator/target/wasm32-wasi/release/calculator.wasm -d adder/target/wasm32-wasi/release/adder.wasm -o composed.wasm
55-
wasm-tools compose command/target/wasm32-wasi/release/command.wasm -d composed.wasm -o final.wasm
52+
wac plug calculator/target/wasm32-wasi/release/calculator.wasm --plug adder/target/wasm32-wasi/release/adder.wasm -o composed.wasm
53+
wac plug command/target/wasm32-wasi/release/command.wasm --plug composed.wasm -o final.wasm
54+
```
55+
56+
Now, run the component with Wasmtime:
57+
58+
```sh
59+
wasmtime run final.wasm 1 2 add
60+
1 + 2 = 3
61+
```
62+
63+
## Composing with the WAC Language
64+
65+
`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.
66+
67+
`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.
68+
69+
```sh
70+
mkdir -p deps/docs
71+
cp adder/target/wasm32-wasi/release/adder.wasm deps/docs/adder-impl.wasm
72+
cp calculator/target/wasm32-wasi/release/calculator.wasm deps/docs/calculator-impl.wasm
73+
cp command/target/wasm32-wasi/release/command.wasm deps/docs/command-impl.wasm
5674
```
5775

58-
Now, run the component with wasmtime:
76+
Now we are ready to construct a WAC file to define our composition. Ours instantiates our three components, declaring
77+
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.
78+
79+
```wac
80+
// Provide a package name for the resulting composition
81+
package example:composition;
82+
83+
// Instantiate the adder-impl component that implements the adder world.
84+
// We are giving this instance the local name `adder-instance`.
85+
let adder-instance = new docs:adder-impl { };
86+
87+
// Instantiate the calculator-impl component that implements the calculator world.
88+
// In the `new` expression, specify the source of the `add` import to be `adder-instance`'s `add` export.
89+
let calculator-instance = new docs:calculator-impl { add: adder-instance.add };
90+
91+
// Instantiate a command-impl component that implements the app world.
92+
// The command component might import other interfaces, such as WASI interfaces, but we want to leave
93+
// those as imports in the final component, so supply `...` to allow those other imports to remain unresolved.
94+
// The command's exports (in this case, `wasi:cli/run`) remain unaffected in the resulting instance.
95+
let command-instance = new docs:command-impl { calculate: calculator-instance.calculate,... };
96+
97+
// Export the `wasi:cli/run` interface from the command instance
98+
// This could also have been expressed using the postfix access expression `command-instance.run`
99+
export command-instance["wasi:cli/run@0.2.0"];
100+
```
101+
102+
Now, perform your composition by passing the WAC file to `wac compose`.
103+
104+
```sh
105+
wac compose composition.wac -o final.wasm
106+
```
107+
108+
> Note, instead of moving all the components to a `deps/docs` directory, you can pass the paths to the components inline
109+
> ```sh
110+
> 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
111+
> ```
112+
113+
Run the component with Wasmtime:
59114
60115
```sh
61116
wasmtime run final.wasm 1 2 add

component-model/examples/tutorial/adder/src/bindings.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
1+
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
22
// Options used:
33
#[allow(dead_code)]
44
pub mod exports {
@@ -149,14 +149,14 @@ macro_rules! __export_adder_impl {
149149
pub(crate) use __export_adder_impl as export;
150150

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

161161
#[inline(never)]
162162
#[doc(hidden)]

component-model/examples/tutorial/calculator/src/bindings.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
1+
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
22
// Options used:
33
#[allow(dead_code)]
44
pub mod docs {
@@ -61,7 +61,8 @@ pub mod exports {
6161
}
6262

6363
impl Op {
64-
pub(crate) unsafe fn _lift(val: u8) -> Op {
64+
#[doc(hidden)]
65+
pub unsafe fn _lift(val: u8) -> Op {
6566
if !cfg!(debug_assertions) {
6667
return ::core::mem::transmute(val);
6768
}
@@ -214,7 +215,7 @@ macro_rules! __export_calculator_impl {
214215
pub(crate) use __export_calculator_impl as export;
215216

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

228229
#[inline(never)]
229230
#[doc(hidden)]

component-model/examples/tutorial/command/src/bindings.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
1+
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
22
// Options used:
33
#[allow(dead_code)]
44
pub mod docs {
@@ -26,7 +26,8 @@ pub mod docs {
2626
}
2727

2828
impl Op {
29-
pub(crate) unsafe fn _lift(val: u8) -> Op {
29+
#[doc(hidden)]
30+
pub unsafe fn _lift(val: u8) -> Op {
3031
if !cfg!(debug_assertions) {
3132
return ::core::mem::transmute(val);
3233
}
@@ -134,15 +135,15 @@ mod _rt {
134135
}
135136

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

147148
#[inline(never)]
148149
#[doc(hidden)]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Provide a package name for the resulting composition
2+
package example:composition;
3+
4+
// Instantiate the adder-impl component that implements the adder world.
5+
// Bind this instance's exports to the local name `adder-instance`.
6+
let adder-instance = new docs:adder-impl { };
7+
8+
// Instantiate the calculator-impl component that implements the calculator world.
9+
// In the `new` expression, connect it's `add` import to the `adder-instance`'s `add` export.
10+
let calculator-instance = new docs:calculator-impl { add: adder-instance.add };
11+
12+
// Instantiate a command-impl component that implements the app world.
13+
// The command component might import other interfaces, such as WASI interfaces, but we want to leave
14+
// those as imports in the final component, so supply `...` to allow those other imports to remain unresolved.
15+
// The command's exports (in this case, `wasi:cli/run`) remain unaffected in the resulting instance.
16+
let command-instance = new docs:command-impl { calculate: calculator-instance.calculate,... };
17+
18+
// Export the `wasi:cli/run` interface from the command instance
19+
// This could also have been expressed using the postfix access expression `command-instance.run`
20+
export command-instance["wasi:cli/run@0.2.0"];

component-model/src/creating-and-consuming/composing.md

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,21 @@ interface validator {
2424
validate-text: func(text: string) -> string;
2525
}
2626
27-
world {
27+
world validator {
2828
export validator;
2929
import docs:regex/match@0.1.0;
3030
}
31+
```
3132

33+
```wit
3234
// component 'regex'
3335
package docs:regex@0.1.0;
3436
3537
interface match {
3638
first-match: func(regex: string, text: string) -> string;
3739
}
3840
39-
world {
41+
world regex {
4042
export match;
4143
}
4244
```
@@ -49,21 +51,54 @@ Component composition tools are in their early stages right now. Here are some
4951
* 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.
5052
* 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:mypackage@0.1.0`. If another Rust component _imports_ an interface from `test:mypackage`, that won't match `test:mypackage@0.1.0`. 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.
5153

52-
## Composing components with `wasm-tools`
54+
## Composing components with WAC
55+
56+
You can use the [WAC](https://github.com/bytecodealliance/wac) CLI to compose components at the command line.
5357

54-
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.
58+
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:
59+
60+
```sh
61+
wac plug validator-component.wasm --plug regex-component.wasm -o composed.wasm
62+
```
5563

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

5866
```sh
59-
wasm-tools compose path/to/component.wasm -d path/to/dep1.wasm -d path/to/dep2.wasm -o composed.wasm
67+
wac plug path/to/component.wasm --plug path/to/dep1.wasm --plug path/to/dep2.wasm -o composed.wasm
6068
```
6169

6270
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`.
6371

64-
> 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!)
72+
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.
73+
74+
### Advanced composition with the WAC language
75+
76+
`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.
77+
78+
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).
79+
80+
```
81+
//composition.wac
82+
// Provide a package name for the resulting composition
83+
package docs:composition;
84+
85+
// Instantiate the regex-impl component that implements the `regex` world. Bind this instance's exports to the local name `regex`.
86+
let regex = new docs:regex-impl { };
87+
88+
// Instantiate the validator-impl component which implements the `validator` world and imports the match interface from the regex component.
89+
let validator = new docs:validator-impl { match: regex.match, ... };
90+
91+
// Export all remaining exports of the validator instance
92+
export validator...;
93+
```
94+
95+
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.
96+
97+
```sh
98+
wac compose --dep docs:regex-impl=regex-component.wasm --dep docs:validator-impl=validator-component.wasm -o composed.wasm composition.wac
99+
```
65100

66-
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).
101+
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).
67102

68103
## Composing components with a visual interface
69104

0 commit comments

Comments
 (0)