wasm-tools の compose コマンド(wasm-compose)を使った WebAssembly コンポーネントの合成を試してみました。
ここでは、item
と app
という 2つの WebAssembly コンポーネントを作成し、wasm-compose を使って app へ item を合成します。
なお、WebAssembly コンポーネントの作成には cargo component を使用します。
item コンポーネント作成
item 側を作成します。
とりあえず、WIT はこのようにしてみました。
指定した商品の内容を取得する処理を itemfind
インターフェースとして定義し、それを export しています。
package component:item;
interface itemfind {
type item-id = string;
type amount = s32;
record item {
id: item-id,
price: amount,
}
find-item: func(id: item-id) -> option<item>;
}
world item {
export itemfind;
}
固定の内容を返すように実装しました。
#[allow(warnings)]
mod bindings;
use bindings::exports::component::item::itemfind::Guest;
use bindings::exports::component::item::itemfind::{Amount, Item, ItemId};
struct Component;
impl Guest for Component {
fn find_item(id: ItemId) -> Option<Item> {
let price: Amount = 123;
let item = Item { id, price };
Some(item)
}
}
bindings::export!(Component with_types_in bindings);
Cargo.toml はこのようになります。
[package]
name = "item"
version = "0.1.0"
edition = "2021"
[dependencies]
bitflags = "2.5.0"
wit-bindgen-rt = "0.24.0"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "component:item"
[package.metadata.component.dependencies]
これをビルドして WebAssembly コンポーネントを作成しておきます。
$ cd item
$ cargo component build --target wasm32-unknown-unknown --release
app コンポーネント作成
次に、app 側を作成します。
こちらの WIT では、item 側で export した itemfind を import します。
また、「cargo componentでWASI Preview 2を使ったコンポーネントを作成」と同じ使い方で WASI Preview 2 を利用しています。1
package component:app;
world app {
import component:item/itemfind;
import wasi:cli/stdout@0.2.0;
export wasi:cli/run@0.2.0;
}
実装はこのようにしてみました。
itemfind インターフェースの find-item
(Rust 上は find_item)で取得した item の内容を wasi:cli/stdout
を使って標準出力しています。
#[allow(warnings)]
mod bindings;
use bindings::component::item::itemfind::find_item;
use bindings::exports::wasi::cli::run::Guest;
use bindings::wasi::cli::stdout::get_stdout;
struct Component;
impl Guest for Component {
fn run() -> Result<(), ()> {
let item = find_item(&"item-1".to_string()).ok_or(())?;
log(&format!("id={}, price={}", item.id, item.price)).map_err(|_| ())?;
Ok(())
}
}
fn log(msg: &str) -> Result<(), Box<dyn std::error::Error>> {
let stdout = get_stdout();
stdout.write(msg.as_bytes())?;
stdout.flush()?;
Ok(())
}
bindings::export!(Component with_types_in bindings);
Cargo.toml はこのようになります。
item の world.wit を参照する必要があるので package.metadata.component.target.dependencies
で設定しています。
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
bitflags = "2.5.0"
wit-bindgen-rt = "0.24.0"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "component:app"
[package.metadata.component.target.dependencies]
"component:item" = { path = "../item/wit" }
"wasi:cli" = { path = "./WASI/preview2/cli" }
...省略
こちらもビルドしておきます。
$ cd app
$ cargo component build --target wasm32-unknown-unknown --release
合成処理
それでは、本題の合成を実施します。
wasm-compose では YAML 形式の設定ファイルで合成内容を指定できます。
今回のケースでは次のような設定を行う必要がありました。
こうする事で、指定の import 箇所へ instance で指定した WebAssembly コンポーネントを合成します。
instantiations:
root:
arguments:
<importの名称>:
instance: <wasmファイル名(拡張子を除く)>
実際はこのようになります。
item.wasm ファイルを見つけられるように search-paths
を設定しています。
search-paths:
- ./item/target/wasm32-unknown-unknown/release
instantiations:
root:
arguments:
component:item/itemfind:
instance: item
instance 部分を省略する事も可能でした。
search-paths:
- ./item/target/wasm32-unknown-unknown/release
instantiations:
root:
arguments:
component:item/itemfind: item
上記 config.yaml の内容で app.wasm へ合成した結果を compose.wasm
ファイルへ出力してみます。
$ wasm-tools compose -c config.yaml -o compose.wasm app/target/wasm32-unknown-unknown/release/app.wasm
...省略
[2024-04-15T13:59:15Z WARN ] instance `wasi:cli/stdout@0.2.0` will be imported because a dependency named `wasi:cli/stdout@0.2.0` could not be found
composed component `compose.wasm`
WASI Preview 2 の合成は設定していないので2、WARN ログが出力されていますが特に問題ありません。
合成結果の確認
compose.wasm を確認してみます。
wasm-tools で WIT を出力してみると、component:item/itemfind の import が消えており、合成は成功しているように見えます。
$ wasm-tools component wit compose.wasm
package root:component;
world root {
import wasi:io/error@0.2.0;
import wasi:io/streams@0.2.0;
import wasi:cli/stdout@0.2.0;
export wasi:cli/run@0.2.0;
}
wasmtime で実行してみると、合成した item から取得した内容が無事に出力されました。
$ wasmtime compose.wasm
id=item-1, price=123
ちなみに、app.wasm を wasmtime で実行すると次のようなエラーになります。
$ wasmtime app/target/wasm32-unknown-unknown/release/app.wasm
Error: failed to run main module `app/target/wasm32-unknown-unknown/release/app.wasm`
Caused by:
0: import `component:item/itemfind` has the wrong type
1: instance export `find-item` has the wrong type
2: expected func found nothing