🧩

Hello, world! with Wasm Component(WITパッケージ編)

2024/04/30に公開

これまでのあらすじ:

  • 初回
    • cargo-componentを使ってWebAssemblyコンポーネント(Wasmコンポーネント)を作りました
    • Wasmtimeで実行しました
    • wasm-toolsを使って作成したコンポーネントのワールドを出力しました
  • 前回
    • cargo-componentを使ってライブラリーとして働くWasmコンポーネントを作成しました
    • WebAssembly Interface Typeでライブラリーのインターフェースを定義し、Rustでインターフェースを実装しました
    • Wasmのパッケージレジストリーであるwa.devに、作成したコンポーネントを登録しました

前回コンポーネントパッケージをレジストリーに登録しました。これを含めて3種類のパッケージをレジストリーに登録できます:

  • コンポーネントパッケージ
  • モジュールパッケージ
  • WITパッケージ

今回はWITパッケージを作成し、wa.devに登録します。そして登録したWITパッケージを実装するコンポーネントを実装し、レジストリーに登録します。

前回までのプロジェクトフォルダー

前回までの内容で、プロジェクトフォルダーは次のようになっています。

.
├── Cargo.lock
├── Cargo.toml
├── crates
│  └── greet
│      ├── Cargo.toml
│      ├── src
│      │  └── lib.rs
│      └── wit
│          └── world.wit
├── src
│  ├── bindings.rs
│  └── main.rs
└── target

前回までにインストールしたツール

前回までに次のツールがインストールされています:

準備

今回の内容のために、以下の準備を行います:

  • wit toolのインストール
  • プロジェクトにWITパッケージ用のフォルダーを追加

wit toolのインストール

前回はwarg-cliを利用してレジストリーにコンポーネントを登録しました。これは上記の3パッケージを管理するツールでした。今回はWITパッケージの管理に特化したツールwit toolを利用します。

wit toolはcargo installコマンドでインストールします:

% cargo install wit

ヘルプを表示して、インストールできたことを確認します:

% wit --help
WIT package tool

Usage: wit <COMMAND>

Commands:
  init     Initialize a new WIT package
  add      Adds a reference to a WIT package from a registry
  build    Build a binary WIT package
  publish  Publish a WIT package to a registry
  key      Manage signing keys for publishing packages to a registry
  update   Update dependencies as recorded in the lock file
  help     Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

WITパッケージ用のフォルダーの作成

プロジェクトにWITパッケージ用のフォルダーを追加します。フォルダー名をwitとしたいところですが、以下の理由からinterfacesというフォルダー名にします:

  • witフォルダーに複数のパッケージに関するWITファイルを保持できないため
  • マニフェストに設定がない場合、cargo-componentはwitフォルダー内で定義されているワールドをターゲットとしてコンポーネントを定義しようとするため

前者に関してはWITの仕様に次のような記述があります:

The current thinking for a convention is that projects will have a wit folder where all wit/*.wit files within describe a single package.

またwitフォルダー内に、複数のパッケージに関するWITファイルがある状態でcargo-componentを実行するとエラーになります。下記の例では、先に発見したcomponent:name2というパッケージとは異なるパッケージcomponent:nameに関する記述を発見したというエラーが表示されています:

% cargo component build
error: failed to create a target world for package `name` (/somewhere/name/Cargo.toml)

Caused by:
    0: failed to parse local target from directory `/Users/chikoski/bbb/name/wit`
    1: package identifier `component:name` does not match previous package name of `component:name2`
            --> /somewhere/name/wit/world.wit:1:9
             |
           1 | package component:name;
             |         ^-------------

username:nameパッケージの定義

新しくusername:nameパッケージを定義します。

interfaces/nameフォルダの作成

interfaces/nameという名前でプロジェクトフォルダに、インターフェースを定義するためのフォルダを作成します。

% pwd 
/somewhere/hello-wasi-cli/
% mkdir -p interfaces/name

作成したフォルダーで、wit initコマンドを実行します。実行すると、WITパッケージのマニフェストが作成されます:

% cd interfaces/name
% wit init
     Created configuration file `./wit.toml`

作成されたマニフェスト

作成直後は、パッケージのバージョン番号だけが記述されています。バージョン番号以外には、インターフェース定義で参照する他のパッケージが記述されます。

version = "0.1.0"

WITファイルの作成

次のようなWITファイルを作成します:

  • パッケージ名はusername:nameとします
  • name-providerインターフェースを定義します。このインターフェースはパラメーターがなく、文字列を返す関数nameを持っています。
  • name-providerをエキスポートするワールドを定義します

上記の内容を満たすWITファイルは次のようになります:

package username:name;

interface name-provider {
    name: func() -> string;
}

world default{
    export name-provider;
}

wa.devでのパッケージ作成

WITパッケージを公開するために、まずwa.devでパッケージを初期化します。初期化はWebインターフェースから行うとわかりやすくて良いと思います。

パッケージの公開

WITファイルが存在するフォルダーで、wit pubishコマンドを実行します。

% pwd
/somewhere/hello-wasi-cli/interfaces/name

% wit publish

公開されたパッケージの例はこちらです。

公開されたWITパッケージの実装

公開されたWITパッケージをRustで実装します。以下の作業は、プロジェクトフォルダー内のcratesフォルダーで行います。

--targetオプションに実装するWITパッケージを指定してcargo component newコマンドを実行します。作成するライブラリークレートの名前はname-implとします。

% pwd 
/someshere/hello-wasi-cli/crates

% cargo component new --lib --target username:name name-impl
    Updating component registry package logs
     Created library `name-impl` package
     Updated manifest of package `name-impl`
   Generated source file `src/lib.rs` for target `username:name` v0.1.0

作成されたライブラリークレートにはwitフォルダーがありません。オプションに指定したWITパッケージからbindings.rsが生成されるためです:

% ls -T name-impl
name-impl
├── Cargo.lock
├── Cargo.toml
└── src
   └── lib.rs

パッケージの実装

lib.rsの初期状態は次のようになっています。Guestトレイトは先ほど定義したusername:nameパッケージに基づいて定義されています。

#[allow(warnings)]
mod bindings;

use bindings::exports::username::name::name_provider::Guest;

struct Component;

impl Guest for Component {
    fn name() -> String {
        unimplemented!()
    }
}

bindings::export!(Component with_types_in bindings);

あとはGuestトレイトを実装します:

#[allow(warnings)]
mod bindings;

use bindings::exports::username::name::name_provider::Guest;

struct Component;

impl Guest for Component {
    fn name() -> String {
        "Wasm".to_string()
    }
}

bindings::export!(Component with_types_in bindings);

ビルド

cargo component buildコマンドでビルドします。

% pwd
/someshare/hello-wasi-cli/crates/name-impl

% cargo component build -r --target wasm32-unknown-unknown

作成されたWasmファイルがname/name-providerをエキスポートしていることが確認できます:

% cd ../..
% pwd
/somewhere/hello-wasi-cli

% wasm-tools component wit target/wasm32-unnown-unknown/release/name_ipml.wasm
package root:component;

world root {
  export username:name/name-provider;
}

まとめ

  • wit toolを使って、WITパッケージをwa.devに公開しました
  • wa.devに公開されているWITパッケージをRustクレートとして使って実装しました
  • Rustクレートをビルドすることで、WITパッケージを実装したWasmコンポーネントを取得しました

Discussion