#wasm-component #bindings-generator #wasm-bindings #wit #wit-bindgen #model #generate

app wit-bindgen-cli

用于生成 WIT 文档和组件模型的绑定 CLI 工具

26 个重大版本

0.30.0 2024 年 8 月 12 日
0.28.0 2024 年 7 月 16 日
0.23.0 2024 年 3 月 27 日
0.16.0 2023 年 12 月 5 日
0.4.0 2023 年 3 月 7 日

#302 in WebAssembly

Download history 143/week @ 2024-05-03 41/week @ 2024-05-10 255/week @ 2024-05-17 152/week @ 2024-05-24 146/week @ 2024-05-31 113/week @ 2024-06-07 61/week @ 2024-06-14 30/week @ 2024-06-21 443/week @ 2024-06-28 161/week @ 2024-07-05 332/week @ 2024-07-12 93/week @ 2024-07-19 133/week @ 2024-07-26 231/week @ 2024-08-02 250/week @ 2024-08-09 125/week @ 2024-08-16

每月 746 次下载

Apache-2.0…

605KB
6.5K SLoC

wit-bindgen

WIT 和组件模型的客户端语言绑定生成器

Bytecode Alliance 项目

build status supported rustc stable

关于

该项目是一套为编译到 WebAssembly 并使用 组件模型 的语言设计的绑定生成器。绑定使用 *.wit 文件 描述,这些文件指定导入、导出,并促进绑定定义之间的重用。

wit-bindgen 存储库目前专注于 客户端 程序,即编译到 WebAssembly 的程序。在宿主中执行组件不在本存储库中管理,有关如何执行的一些选项在 下面描述。本存储库中开发的语言包括 Rust、C、Java (TeaVM Java)、Go (TinyGo) 和 C#。如果您遇到任何问题,请随时 提交问题 或在我们 Zulip 上聊天。

WIT 作为 IDL

项目 wit-bindgen 广泛使用了 WIT 定义来描述导入和导出。由 WIT 支持的项目直接映射到组件模型,这使得由本地编译器产生的核心 WebAssembly 二进制文件可以转换为组件。WebAssembly 二进制文件的所有导入和导出都必须使用 WIT 进行描述。一个示例文件看起来像

package example:host;

world host {
  import print: func(msg: string);

  export run: func();
}

这描述了一个“世界”,其中包含了 WebAssembly 组件将可用的导入和导出。在这种情况下,宿主将提供一个 print 函数,而组件本身将提供一个 run 函数。

WIT 中的功能也可以组织到 interface

package example:my-game;

interface my-plugin-api {
  record coord {
    x: u32,
    y: u32,
  }

  get-position: func() -> coord;
  set-position: func(pos: coord);

  record monster {
    name: string,
    hp: u32,
    pos: coord,
  }

  monsters: func() -> list<monster>;
}

world my-game {
  import print: func(msg: string);
  import my-plugin-api;

  export run: func();
}

这里,my-plugin-api 接口封装了一组函数、类型等。然后可以通过 my-plugin-api 命名空间将它们全部导入到 my-game 世界中。一个 WIT 文档和世界的结构将影响每个语言的生成绑定。

有关 WIT 及其语法的更多信息,请参阅 WIT 在线文档 以及其 上游参考

创建组件

wit-bindgen 的最终目标是便于创建 组件。一旦创建了组件,就可以将其移交给任何宿主运行时进行执行。然而,目前没有任何语言支持以原生方式创建组件,因此 wit-bindgen 只是创建组件过程中的一个组成部分。对于编译语言的组件构建过程的一般概述是

  1. 使用 wit-bindgen 为该语言生成代表指定 API 的绑定的源代码。然后,由本地编译器编译此源代码,并由用户编写的代码使用。
  2. 使用本地语言工具链生成一个核心 WebAssembly 模块。这个核心 wasm 模块是组件的“主体”,包含了所有编译成 WebAssembly 的用户定义代码。目前最常用的编译目标是 wasm32-wasip1
  3. 使用 wasm-tools 项目(特别是 wasm-tools component new 子命令)将输出核心 wasm 模块转换为组件。这将消耗本地的核心 wasm 输出并将其包装成组件模型二进制格式。

每个步骤的精确工具和命令各不相同,具体取决于语言,但这是基本思路。有了组件,二进制文件就可以交给宿主运行时执行。

创建组件:WASI

今天创建组件时,WASI 是一个重要的考虑因素。目前所有具有 WASI 支持的语言的本地工具链都在使用 wasi_snapshot_preview1 版本的 WASI。这个 WASI 定义是用历史 *.witx 文件完成的,并且与组件模型不兼容。然而,有一种方法可以创建使用 wasi_snapshot_preview1 API 的模块的组件。

《wasm-tools component new》子命令接受一个--adapt参数,该参数作为一种方法,用于用组件模型API填充非组件模型API,例如wasi_snapshot_preview1。Wasmtime运行时在每次发布时都会发布适配器模块,这些模块适用于--adapt,以WASI 0.2的形式实现wasi_snapshot_preview1。在Wasmtime的发布页面上,您将看到三个模块可供选择

只需要一个适配器,并确保查找最新版本

支持的客户端语言

《wit-bindgen》项目主要关注客户端语言,这些语言被编译成WebAssembly。这里列出的每种语言在核心wasm层(例如,针对当前核心wasm规范)都已有原生支持在WebAssembly中执行。这里还列出了每个语言的简要说明,说明如何使用它。

以下每个项目都假设您的项目根目录中存在以下*.wit文件。

// wit/host.wit
package example:host;

world host {
  import print: func(msg: string);

  export run: func();
}

客户端:Rust

Rust编译器支持本地的wasm32-wasip1目标,可以通过以下方式将其添加到任何基于rustup的工具链中

rustup target add wasm32-wasip1

为了编译wasi动态库,必须在Cargo.toml文件中添加以下内容

[lib]
crate-type = ["cdylib"]

项目可以通过执行以下命令来依赖于wit-bindgen

cargo add wit-bindgen

WIT文件目前被添加到与您的Cargo.toml文件相邻的wit/文件夹中。使用此文件的示例代码如下

// src/lib.rs

// Use a procedural macro to generate bindings for the world we specified in
// `host.wit`
wit_bindgen::generate!({
    // the name of the world in the `*.wit` input file
    world: "host",
});

// Define a custom type and implement the generated `Guest` trait for it which
// represents implementing all the necessary exported interfaces for this
// component.
struct MyHost;

impl Guest for MyHost {
    fn run() {
        print("Hello, world!");
    }
}

// export! defines that the `MyHost` struct defined below is going to define
// the exports of the `world`, namely the `run` function.
export!(MyHost);

通过使用cargo expandcargo doc,您还可以探索生成的代码。如果wit-bindgen中存在错误,生成的绑定无法编译,或者在生成的代码中存在错误(这可能是wit-bindgen中的错误),您可以使用环境变量WIT_BINDGEN_DEBUG=1来帮助调试。

然后可以使用以下命令构建该项目

cargo build --target wasm32-wasip1
wasm-tools component new ./target/wasm32-wasip1/debug/my-project.wasm \
    -o my-component.wasm --adapt ./wasi_snapshot_preview1.reactor.wasm

这会创建一个my-component.wasm文件,该文件可以在任何组件运行时中执行。使用wasm-tools,您还可以检查该二进制文件,例如推断出组件的WIT世界

wasm-tools component wit my-component.wasm
# world my-component {
#  import print: func(msg: string)
#  export run: func()
# }

在这种情况下,如预期的那样,它与输入世界相同。

客户端:C/C++

C和C++代码可以使用WASI SDK项目进行编译,以wasm32-wasip1为目标。该仓库中的版本包含了预编译的clang二进制文件,这些文件已预先配置为编译WebAssembly。

要在C和C++中开始,会为你的项目生成一个*.c和一个*.h头文件。这些文件是用这个仓库中的wit-bindgen CLI命令生成的。

wit-bindgen c ./wit
# Generating "host.c"
# Generating "host.h"
# Generating "host_component_type.o"

使用这个方法的示例代码如下

// my-component.c

#include "host.h"

void host_run() {
    host_string_t my_string;
    host_string_set(&my_string, "Hello, world!");

    host_print(&my_string);
}

然后可以使用WASI SDK中的clang进行编译,并使用以下命令将其组装成一个组件

clang host.c host_component_type.o my-component.c -o my-core.wasm -mexec-model=reactor
wasm-tools component new ./my-core.wasm -o my-component.wasm

与Rust类似,你可以检查输出二进制文件

wasm-tools component wit ./my-component.wasm

访客:Java

Java字节码可以使用TeaVM-WASI编译成WebAssembly。使用此生成器,wit-bindgen将生成*.java文件,这些文件可以与任何JVM语言(例如Java、Kotlin、Clojure、Scala等)一起使用。

访客:TinyGo

你可以使用TinyGo编译器将Go代码编译成一个Wasm模块。例如,以下命令将main.go编译成WASI模块

tinygo build-目标=wasi main.go

注意:当前的TinyGo bindgen需要TinyGo版本v0.27.0或更高版本。

当使用wit-bindgen tiny-go bindgen时,将生成*.go*.h C头文件供你的项目使用。这些文件是用这个仓库中的wit-bindgen CLI命令生成的。

wit-bindgen tiny-go ./wit
# Generating "host.go"
# Generating "host.c"
# Generating "host.h"
# Generating "host_component_type.o"

如果你的Go代码使用了resultoption类型,将生成一个额外的Go文件host_types.go。该文件包含与WIT文件中的resultoption类型相对应的Go类型。

使用生成的Go代码的示例如下

初始化Go

go mod init example.com

创建你的Go主文件

// my-component.go
package main

import (
	api "example.com/api"
)

func init() {
    a := HostImpl{}
    api.SetHost(a)
}

type HostImpl struct {
}

func (e HostImpl) Run() {
  api.HostPrint("Hello, world!")
}

//go:generate wit-bindgen tiny-go wit --out-dir=api
func main() {}

此设置允许你调用go generate,它将生成Go代码的绑定到api目录。之后,你可以使用TinyGo编译器将Go代码编译成WASI模块。最后,你可以使用wasm-tools组件化模块。

go generate # generate bindings for Go
tinygo build -target=wasi -o main.wasm my-component.go # compile
wasm-tools component embed --world host ./wit main.wasm -o main.embed.wasm # create a component
wasm-tools component new main.embed.wasm --adapt wasi_snapshot_preview1.command.wasm -o main.component.wasm
wasm-tools validate main.component.wasm --features component-model

访客:其他语言

希望有一天可以使用wit-bindgen或通用组件支持其他语言,如JS、Ruby、Python等。如果你有兴趣为这些语言之一贡献生成器,建议在zulip上联系。但是,值得注意的是,将解释型语言转换为组件与编译型语言(例如Rust或C/C++)的工作方式有很大不同。预计第一个解释型语言将需要大量的设计工作,但一旦实现,其他语言可以相对快速地按照第一个设计进行。

CLI安装

要安装此工具的CLI(这并非使用它的唯一方法),请运行以下cargo命令。这将允许你为任何受支持的语言生成绑定。

cargo install wit-bindgen-cli

此CLI 不稳定,可能会更改,请不要期待它稳定或依赖其稳定性。如果您想依赖它,请通过Zulip联系我们,以便我们为您的情况找到更好的替代方案。

组件的宿主运行时

wit-bindgen项目旨在帮助生成组件,但一旦组件在您手中,下一步就是实际在某处执行它。这不在wit-bindgen本身的范围内,但以下是一些资源和运行时,可以帮助您使用组件。

  • Rust:[wasmtime](https://docs.rs/wasmtime)crate是一个原生组件运行时的实现,可以运行任何WITworld。它还提供了一个[bindgen!](https://docs.rs/wasmtime/latest/wasmtime/component/macro.bindgen.html)宏,与该存储库中的[generate!](https://component-model.bytecodealliance.org/design/wit.html)宏类似。此宏接收一个WIT包作为输入,并为运行时生成基于trait的绑定,以实现和使用。

  • JS:[jco](https://github.com/bytecodealliance/jco)项目可以用来在JS中执行组件,无论是在网页上还是在浏览器之外的运行时(如node)中。该项目通过提取组成组件的核心WebAssembly模块,并生成JS粘合剂以在主机和这些模块之间进行交互,为单个具体组件生成polyfill以在JS环境中执行。

  • Python:[wasmtime-py](https://github.com/bytecodealliance/wasmtime-py)项目[在PyPI](http://pypi.ac.cn/project/wasmtime/)上的bindgen模式与JS集成类似。给定一个具体组件,它将生成Python源代码,使用Wasmtime的嵌入式核心WebAssembly支持与组件交互。

  • 工具:[wasm-tools](https://github.com/bytecodealliance/wasm-tools)项目可以用来检查和修改组件的低级细节。例如,如前所述,您可以使用wasm-tools component wit检查组件的WIT-based接口。您还可以使用wasm-tools compose将两个组件链接在一起。

请注意,上述运行时通常旨在与任意组件一起工作,而不仅仅是那些由wit-bindgen创建的组件。这也不一定是执行组件的所有可能性的详尽列表。

构建和测试

要构建CLI

cargo build

有关如何在测试文档中运行测试的更多信息,请参阅测试文档

版本控制和发布

此存储库中的crate和CLI目前都使用版本0.X.Y进行版本控制,其中Y通常是0,而X在发布时通常会递增。这意味着,随着此处的发展,更改以可能破坏API的方式发布。

此外,此存储库目前没有严格的发布时间表。根据需要发布。如果您想进行发布,请随时通过Zulip、提交问题、在PR上留下评论或以其他方式联系维护者。

对于维护者,发布流程如下

  • 转到此链接
  • 在UI中单击“运行工作流程”。
  • 使用默认的 bump 参数,然后点击“运行工作流程”
  • 等待 CI 创建一个 PR。你可以通过“操作”选项卡查看是否出错。
  • 当 PR 打开时,关闭它然后重新打开。不要提问。
  • 审查 PR,批准它,然后排队合并。

这就够了,但一定要密切关注 CI,以防出现任何问题。

许可证

本项目在 Apache 2/Apache 2 加 LLVM 例外/MIT 许可证下三重许可。这样做的原因是

  • Apache 2/MIT 在 Rust 生态系统中很常见。
  • Apache 2/MIT 用于 Rust 标准库,并且部分代码可能会迁移到那里。
  • 部分代码可能用于编译器输出,Apache 2 加 LLVM 例外许可证对此很有用。

更多详情请参阅

贡献

除非你明确声明,否则你提交给本项目以供包含的任何贡献,根据 Apache 2/Apache 2 加 LLVM 例外/MIT 许可证的定义,应按上述方式许可,不附加任何额外条款或条件。

依赖项

~8MB
~139K SLoC