From 290036eecc14e55ed5111d392ee4d052b5127670 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 26 Jan 2023 09:27:51 -0800 Subject: [PATCH] Rewrite the project README This commit provides a much-needed update to the `README.md` of wit-bindgen. The README now mentions WIT, components, the current tooling workflow, and an updated "tutorial" for languages like Rust and C. Overall it's intended to generally be more useful and up-to-date. This commit additionally updates to the latest ABI for `wit-component` and fixes a few minor issues here and there with the CLI. --- Cargo.toml | 2 +- README.md | 401 +++++++++++++++++++++++-------- crates/gen-guest-c/src/lib.rs | 8 +- crates/gen-guest-rust/src/lib.rs | 2 +- crates/guest-rust/src/lib.rs | 12 +- src/bin/wit-bindgen.rs | 98 ++++---- 6 files changed, 366 insertions(+), 157 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a318d7c13..b03c0b2c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ indexmap = "1.9.1" wasm-encoder = "0.22.0" wat = "1.0.56" wit-parser = "0.4.1" -wit-component = "0.4.3" +wit-component = "0.4.4" wit-bindgen-core = { path = 'crates/bindgen-core', version = '0.3.0' } wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', version = '0.3.0' } diff --git a/README.md b/README.md index 7a551893f..9a7f9a17e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@

wit-bindgen

- A language bindings generator for wit + Guest language bindings generator for + WIT + and the + Component Model +

A Bytecode Alliance project @@ -20,152 +24,349 @@ > this time it's recommended to reach out to the authors on [zulip] and get more > information first. -[zulip]: https://bytecodealliance.zulipchat.com/ +[zulip]: https://bytecodealliance.zulipchat.com/#narrow/stream/327223-wit-bindgen -This project is a bindings generator framework for WebAssembly programs and -embeddings of WebAssembly. This works with [`*.wit`](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md), which describe the -interface of a module, either imported or exported. In the future, it will support [profiles](https://github.com/WebAssembly/component-model/pull/83) (`*.world`), which define the collection of possible imports and exports for a module or component. +This project is a suite of bindings generators for languages that are compiled +to WebAssembly and use the [component model]. Bindings are described with +[`*.wit` files][WIT] which specify imports, exports, and facilitate reuse +between bindings definitions. -## Hosts and Guests +[WIT]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md +[component model]: https://github.com/WebAssembly/component-model -When thinking about WebAssembly, it can be useful to talk in terms of **hosts** and **guests**. A **host** exists outside the WebAssembly runtime and can instantiate modules, satisfying their imports and using their exports. The WebAssembly that runs inside the runtime is called the **guest**. Both **hosts** and **guests** can both have imports and exports. For example, a **guest** can import a WASI interface that the **host** exports to it. It's also possible for **guests** to export/import things to/from other **guests**. +The `wit-bindgen` repository is currently focused on **guest** programs which +are those compiled to WebAssembly. Executing a component in a host is not +managed in this repository, and some options of how to do so are [described +below][hosts]. -## Use Cases +## [WIT] as an IDL -This project can be used in cases such as: +The `wit-bindgen` project extensively uses [WIT] definitions to describe imports +and exports. The items supported by [WIT] directly map to the component model +which allows core WebAssembly binaries produce by native compilers to be +transformed into a component. All imports into a WebAssembly binary and all +exports must be described with [WIT]. An example file looks like: -* You're implementing a guest in Rust that will be compiled to WebAssembly and you'd like it to import WASI. The command `wit-bindgen guest rust --import ` will generate Rust bindings so your guest can import WASI APIs that are described by a `*.wit` file. - -* You're creating a host in Python that uses the wasmtime runtime and wants to provide WASI functionality to guests. The command `wit-bindgen host wasmtime-py --export ` will generate the Python stubs needed to implement and pass in the WASI interface. +``` +default world host { + import print: func(msg: string) -* You're writing JS host code in the browser that will consume a WebAssembly module and you don't want to deal with funky ABI details. The command `wit-bindgen host js` can generate the JS bindings and a TypeScript interface for you with native JS types. + export run: func() +} +``` -**Note:** This CLI experience is not the only way wit-bindgen can/will be used. For example it can be used in Rust through procedural macros and potentially in the future Cargo dependencies. Usage in a Web application would probably be through a version of wit-bindgen compiled to WebAssembly and published to NPM. +This describes a "world" which describes both imports and exports that the +WebAssembly component will have available. In this case the host will provide a +`print` function and the component itself will provide a `run` function. -## Context -The purpose of `wit-bindgen` is to provide a forwards-compatible toolchain and story for modules using the canonical ABI and eventually components in the emerging [Component Model](https://github.com/WebAssembly/component-model). This project was originally based on the [interface types -proposal](https://github.com/webassembly/interface-types) and the [canonical ABI]. The Component Model will eventually "absorb" the interface types proposal, so all references to interface types are effectively to interface types / the component model. This repository will be following upstream changes there, especially for the [`*.wit`](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md) syntax. +Functionality in [WIT] can also be organized into `interface`s: -Currently, generated language bindings all use the [canonical ABI] for communication. This means that any language with supported guest bindings can be consumed in any environment with supported host bindings, which will interoperate through the [canonical ABI]. +``` +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 +} + +default world my-game { + import print: func(msg: string) + import plugin: self.my-plugin-api + + export run: func() +} +``` -[canonical ABI]: https://github.com/WebAssembly/interface-types/pull/132 +Here the `my-plugin-api` interface encapsulates a group of functions, types, +etc. This can then be imported wholesale into the `my-game` world via the +`plugin` namespace. The structure of a [WIT] document and world will affect the +generated bindings per-language. + +For more information about WIT and its syntax see the [upstream description of +WIT][WIT]. + +## Creating a Component + +The end-goal of `wit-bindgen` is to facilitate creation of a +[component][component model]. Once a component is created it can then be handed +off to any one of a number of [host runtimes][hosts] for execution. Creating a +component is not supported natively by any language today, however, so +`wit-bindgen` is only one of the pieces in the process of creating a component. +The general outline for the build process of a component for a compiled language +is: + +1. Using `wit-bindgen` source code for the language is generated representing + bindings to the specified APIs. This source code is then compiled by the + native compiler and used by user-written code as well. +2. The native language toolchain is used to emit a core WebAssembly module. This + core wasm module is the "meat" of a component and contains all user-defined + code compiled to WebAssembly. The most common native target to use for + compilation today is the `wasm32-wasi` target. +3. The output core wasm module is transformed into a component using the + [`wasm-tools`] project, notably the `wasm-tools component new` subcommand. + This will ingest the native core wasm output and wrap the output into the + component model binary format. + +[`wasm-tools`]: https://github.com/bytecodealliance/wasm-tools + +The precise tooling and commands at each of these steps [differs language by +language][guests], but this is the general idea. With a component in-hand the +binary can then be handed off to [a host runtimes][hosts] for execution. + +### Creating components: WASI + +An important consideration when creating a component today is WASI. All current +native toolchains for languages which have WASI support are using the +`wasi_snapshot_preview1` version of WASI. This definition of WASI was made +with historical `*.witx` files and is not compatible with the component model. +There is, however, a means by which to still create components from modules +that are using `wasi_snapshot_preview1` APIs. + +The `wasm-tools component new` subcommand takes an `--adapt` argument which acts +as a way to polyfill non-component-model APIs, like `wasi_snapshot_preview1`, +with component model APIs. The [preview2-prototyping] project is the current +go-to location to to acquire a polyfill from `wasi_snapshot_preview1` to an +in-development version of "wasi preview2" which is specified with [WIT] +and the component model. + +Notably you'll want to download [one of the adapter modules][preview1-build] +and name it `wasi_snapshot_preview1.wasm` locally to pass as an `--adapt` +argument to `wasm-tools component new`. Note that there are two modules +provided on the downloads page, [one is for non-commands][non-command] which +don't have a `_start` entrypoint in the generated core wasm module (e.g. the +`cdylib` crate type in Rust) and [one that is for command modules][command] +which has a `_start` entrypoint (e.g. a `src/main.rs` in Rust). + +[preview2-prototyping]: https://github.com/bytecodealliance/preview2-prototyping +[preview1-build]: https://github.com/bytecodealliance/preview2-prototyping/releases/tag/latest +[non-command]: https://github.com/bytecodealliance/preview2-prototyping/releases/download/latest/wasi_snapshot_preview1.wasm +[command]: https://github.com/bytecodealliance/preview2-prototyping/releases/download/latest/wasi_snapshot_preview1.command.wasm + +## Supported Guest Languages +[guests]: #supported-guest-languages + +The `wit-bindgen` project is primarily focused on **guest** languages which are +those compiled to WebAssembly. Each language here already has native support for +execution in WebAssembly at the core wasm layer (e.g. targets the current [core +wasm specification](https://webassembly.github.io/spec/)). Brief instructions +are listed here for each language of how to use it as well. + +Each project below will assume the following `*.wit` file in the root of your +project. -## Demo +``` +// wit/host.wit +default world host { + import print: func(msg: string) -[View generated bindings -online!](https://bytecodealliance.github.io/wit-bindgen/) + export run: func() +} +``` -If you're curious to poke around and see what generated bindings look like for a -given input `*.wit`, you can explore the generated code online to get an idea -of what's being generated and what the glue code looks like. +### Guest: Rust -## CLI Installation +The Rust compiler supports a native `wasm32-wasi` target and can be added to any +`rustup`-based toolchain with: -To install the CLI for this tool (which isn't the only way it can be used), run the following cargo command. This will let you generate the bindings for any supported language. +```sh +$ rustup target add wasm32-wasi +``` -This CLI **IS NOT** stable and may change, do not expect it to be or rely on it being stable. Please reach out to us on [zulip] if you'd like to depend on it, so we can figure out a better alternative for your use case. +Projects can then depend on `wit-bindgen` by executing: +```sh +$ cargo add --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-guest-rust ``` -cargo install --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-cli + +WIT files are currently added to a `wit/` folder adjacent to your `Cargo.toml` +file. Example code using this then looks like: + +```rust +// src/lib.rs + +// Use a procedural macro to generate bindings for the world we specified in +// `host.wit` +wit_bindgen_guest_rust::generate!("host"); + +// Define a custom type and implement the generated `Host` trait for it which +// represents implementing all the necesssary exported interfaces for this +// component. +struct MyHost; + +impl Host for MyHost { + fn run() { + print("Hello, world!"); + } +} + +export_host!(MyHost); ``` -## Supported Generators +By using [`cargo expand`](https://github.com/dtolnay/cargo-expand) or `cargo +doc` you can also explore the generated code. +This project can then be built with: +```sh +$ cargo build ---target wasm32-wasi +$ wasm-tools component new ./target/wasm32-wasi/debug/my-project.wasm \ + -o my-component.wasm --adapt ./wasi_snapshot_preview1.wasm +``` -### Guests +This creates a `my-component.wasm` file which is suitable to execute in any +component runtime. Using `wasm-tools` you can inspect the binary as well, for +example inferring the WIT world that is the component: -These generators are for creating guest modules that import/export WIT types. +```sh +$ wasm-tools component wit my-component.wasm +default world my-component { + import print: func(msg: string) + export run: func() +} +``` -* `rust` - this is for Rust compiled to WebAssembly, typically using either - the `wasm32-wasi` or `wasm32-unknown-unknown` targets depending on your use - case. In this mode you'd probably depend on the `wit-bindgen-guest-rust` crate - (located at `crates/guest-rust`) and use the `import!` and `export!` macros to - generate code. +which in this case, as expected, is the same as the input world. -* `c` - this is for C compiled to WebAssembly, using either of the targets above - for Rust as well. With C the `wit-bindgen` CLI tool will emit a `*.h` and a - `*.c` file to be compiled into the wasm module. +### Guest: C/C++ -* `teavm-java` - this is for Java bytecode compiled to WebAssembly using - [TeaVM-WASI](https://github.com/fermyon/teavm-wasi). With this generator, - `wit-bindgen` will emit a `*.java` file which may be used with any JVM - language, e.g. Java, Kotlin, Clojure, Scala, etc. +C and C++ code can be compiled for the `wasm32-wasi` target using the [WASI +SDK] project. The releases on that repository have precompiled `clang` binaries +which are pre-configured to compile for WebAssembly. -### Hosts +[WASI SDK]: https://github.com/webassembly/wasi-sdk -These generators are for hosts interacting with modules that import/export WIT types. +To start in C and C++ a `*.c` and `*.h` header file is generated for your +project to use. These files are generated with the [`wit-bindgen` CLI +command][cli-install] in this repository. -* `wasmtime-rust` - this is for Rust users using the `wasmtime` crate. This generator - can also be is used through the `wit-bindgen-host-wasmtime-rust` crate (located at - `crates/host-wasmtime-rust`) and, like the guest Rust support, has an - `import!` and an `export!` macro for generating code. +```sh +$ wit-bindgen c ./wit +Generating "host.c" +Generating "host.h" +Generating "host_component_type.o" +``` -* `js` - this is for JavaScript users executing WebAssembly modules. This could - be in a browsers, Node.js, or Deno. In theory this covers browser use cases - like web workers and such as well. In this mode the `wit-bindgen` CLI tool - will emit a `*.js` and a `*.d.ts` file describing the interface and providing - necessary runtime support in JS to implement the canonical ABI. Note that the - intended long-term integration of this language is to compile `wit-bindgen` - itself to WebAssembly and publish NPM packages for popular JS build systems to - integrate `wit-bindgen` into JS build processes. +Some example code using this would then look like -* `wasmtime-py` - this is for Python users using the `wasmtime` PyPI package. - This uses Wasmtime under the hood but you get to write Python in providing - imports to WebAssembly modules or consume modules using interface types. This - generates a `*.py` file which is annotated with types for usage in `mypy` or - other type-checkers. +```c +// my-component.c -### Other +#include "host.h" -Finally in a sort of "miscellaneous" category the `wit-bindgen` CLI also -supports: +void host_run() { + host_string_t my_string; + host_string_set(&my_string, "Hello, world!"); -* `markdown` - generates a `*.md` and a `*.html` file with readable - documentation rendered from the comments in the source `*.wit` file. + host_print(&my_string); +} +``` -### Arguments -All generators support the `--import` and `--export` flags in the `wit-bindgen` -CLI tool: +This can then be compiled with `clang` from the [WASI SDK] and assembled into a +component with: +```sh +$ 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 ``` -$ wit-bindgen host js --import browser.wit -$ wit-bindgen guest rust --export my-interface.wit -$ wit-bindgen host rust --import host-functions.wit + +Like with Rust, you can then inspect the output binary: + +```sh +$ wasm-tools component wit ./my-component.wasm ``` -Here "import" means "I want to import and call the functions in this interface" -and "export" means "I want to define the functions in this interface for others -to call". +### Guest: Java + +Java bytecode can be compiled to WebAssembly using +[TeaVM-WASI](https://github.com/fermyon/teavm-wasi). With this generator, +`wit-bindgen` will emit a `*.java` file which may be used with any JVM language, +e.g. Java, Kotlin, Clojure, Scala, etc. -### Contributing Bindings +### Guest: TinyGo -The list of supported languages here is a snapshot in time and is not -final. The purpose of the interface-types proposal is to be language agnostic -both in how WebAssembly modules are written as well as how they are consumed. If -you have a runtime that isn't listed here or you're compiling to WebAssembly and -your language isn't listed here, it doesn't mean that it will never be -supported! +The Go language currently can be compiled to standalone out-of-browser +WebAssembly binaries using the [TinyGo](https://tinygo.org/) toolchain. +Support does not yet exist in this repository for TinyGo but is being worked on +[in a PR](https://github.com/bytecodealliance/wit-bindgen/pull/321) and is +expected to land in this repository at some point in the future. -Writing language bindings generators is not trivial, but the crates and tooling in this repository exist to make writing generators as easy as practically possible. If you are interested in support for a language or runtime, please check our issues and file one if there isn't already an issue for it. +### Guest: Other Languages -Here is a non-exhaustive list of some generators that we don't currently support in `wit-bindgen` today but are possible in the future. +Other languages such as JS, Ruby, Python, etc, are hoped to be supported one day +with `wit-bindgen` or with components in general. It's recommended to reach out +on [zulip] if you're intersted in contributing a generator for one of these +langauges. It's worth noting, however, that turning an interpreted language into +a component is significantly different from how compiled languages currently +work (e.g. Rust or C/C++). It's expected that the first interpreted language +will require a lot of design work, but once that's implemented the others can +ideally relatively quickly follow suit and stay within the confines of the +first design. -* `host wasmtime-go` - same as for `host wasmtime-py` but for Go. - Basically for Go users using the [`wasmtime-go` package](https://github.com/bytecodealliance/wasmtime-go) who want to work with interface types rather than raw pointers/memories/etc. +## CLI Installation +[cli-install]: #cli-installation -* `host wasmtime-cpp` - again the same as for `host wasmtime-py`, but for users of the - [`wasmtime-cpp` header file](https://github.com/alexcrichton/wasmtime-cpp) to - use interface types from C++. +To install the CLI for this tool (which isn't the only way it can be used), run +the following cargo command. This will let you generate the bindings for any +supported language. -* `guest js` - while host runtime support is provided for JS today it should also be - supported for [JS compiled to WebAssembly](https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly). - For example a `*.d.ts` file could be generated for what JS projects could - import and then corresponding glue code for the engine-compiled-to-wasm would - also be generated. This means that you could use both JS-in-wasm but also JS - as a host (or more realistically another runtime like Wasmtime since if you're - running in a JS environment you're probably best off running the JS there - instead). +``` +cargo install --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-cli +``` -There are a plethora of other languages that compile to WebAssembly and runtimes. Since interface types should be able to work with all of them, they're theoretically just some work-hours away from having support in `wit-bindgen`. +This CLI **IS NOT** stable and may change, do not expect it to be or rely on it +being stable. Please reach out to us on [zulip] if you'd like to depend on it, +so we can figure out a better alternative for your use case. + + +## Host Runtimes for Components +[hosts]: #host-runtimes-for-components + +The `wit-bindgen` project is intended to facilitate in generating a component, +but once a component is in your hands the next thing to do is to actually +execute that somewhere. This is not under the purview of `wit-bindgen` itself +but these are some resources and runtimes which can help you work with +components: + +* Rust: the [`wasmtime` crate](https://docs.rs/wasmtime) is an implementation of + a native component runtime that can run any WIT `world`. It additionally comes + with a [`bindgen!` + macro](https://docs.rs/wasmtime/latest/wasmtime/component/macro.bindgen.html) + which acts similar to the `generate!` macro in this repository. This macro + takes a [WIT] package as input and generates `trait`-based bindings for the + runtime to implement and use. + +* JS: the [`js-component-tools`] project can be used to execute components in JS + either on the web or outside the browser in a runtime such as `node`. This + project generates a polyfill for a single concrete component to execute in a + JS environment by extracting the core WebAssembly modules that make up a + component and generating JS glue to interact between the host and these + modules. + +* Python: the [`wasmtime`](https://github.com/bytecodealliance/wasmtime-py) + project [on PyPI](https://pypi.org/project/wasmtime/) has a `bindgen` mode + that works similar to the JS integration. Given a concrete component this will + generate Python source code to interact with the component using an embedding + of Wasmtime for its core WebAssembly support. + +* Tooling: the [`wasm-tools`] project can be used to inspect and modify + low-level details of components. For example as previously mentioned you can + inspect the WIT-based interface of a component with `wasm-tools component + wit`. You can link two components together with `wasm-tools compose` as well. + +[`js-component-tools`]: https://github.com/bytecodealliance/js-component-tools + +Note that the runtimes above are generally intended to work with arbitrary +components, not necessarily only those created by `wit-bindgen`. This is also +not necessarily an exhaustive listing of what can execute a component. diff --git a/crates/gen-guest-c/src/lib.rs b/crates/gen-guest-c/src/lib.rs index cd671fd91..833f5757d 100644 --- a/crates/gen-guest-c/src/lib.rs +++ b/crates/gen-guest-c/src/lib.rs @@ -90,7 +90,7 @@ impl WorldGenerator for C { if i == 0 { uwriteln!(gen.src.h_fns, "\n// Imported Functions from `{name}`"); } - gen.import(func); + gen.import(name, func); } gen.finish(); @@ -112,7 +112,7 @@ impl WorldGenerator for C { if i == 0 { uwriteln!(gen.src.h_fns, "\n// Imported Functions from `{name}`"); } - gen.import(func); + gen.import("$root", func); } gen.finish(); @@ -699,7 +699,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { } impl InterfaceGenerator<'_> { - fn import(&mut self, func: &Function) { + fn import(&mut self, wasm_import_module: &str, func: &Function) { let sig = self.resolve.wasm_signature(AbiVariant::GuestImport, func); self.src.c_fns("\n"); @@ -710,7 +710,7 @@ impl InterfaceGenerator<'_> { uwriteln!( self.src.c_fns, "__attribute__((import_module(\"{}\"), import_name(\"{}\")))", - self.name, + wasm_import_module, func.name ); let import_name = self.gen.names.tmp(&format!( diff --git a/crates/gen-guest-rust/src/lib.rs b/crates/gen-guest-rust/src/lib.rs index 236531a7c..cda7ac2d5 100644 --- a/crates/gen-guest-rust/src/lib.rs +++ b/crates/gen-guest-rust/src/lib.rs @@ -143,7 +143,7 @@ impl WorldGenerator for RustWasm { funcs: &[(&str, &Function)], _files: &mut Files, ) { - let mut gen = self.interface(Some(""), resolve, TypeMode::AllBorrowed("'a"), true); + let mut gen = self.interface(Some("$root"), resolve, TypeMode::AllBorrowed("'a"), true); for (_, func) in funcs { gen.generate_guest_import(func); diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 0f0f01b2b..f3ea0efe3 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -36,7 +36,17 @@ pub mod rt { alloc::realloc(old_ptr, layout, new_len) }; if ptr.is_null() { - alloc::handle_alloc_error(layout); + // Print a nice message in debug mode, but in release mode don't + // pull in so many dependencies related to printing so just emit an + // `unreachable` instruction. + if cfg!(debug_assertions) { + alloc::handle_alloc_error(layout); + } else { + #[cfg(target_arch = "wasm32")] + core::arch::wasm32::unreachable(); + #[cfg(not(target_arch = "wasm32"))] + unreachable!(); + } } return ptr; } diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 231129db6..c53f5963b 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -12,35 +12,50 @@ fn version() -> &'static str { #[derive(Debug, Parser)] #[command(version = version())] -struct Opt { - #[command(subcommand)] - category: Category, - #[clap(flatten)] - common: Common, - #[clap(flatten)] - world: WorldOpt, -} - -#[derive(Debug, Parser)] -enum Category { +enum Opt { /// This generator outputs a Markdown file describing an interface. #[cfg(feature = "markdown")] - Markdown(wit_bindgen_gen_markdown::Opts), + Markdown { + #[clap(flatten)] + opts: wit_bindgen_gen_markdown::Opts, + #[clap(flatten)] + args: Common, + }, /// Generates bindings for Rust guest modules. #[cfg(feature = "rust")] - Rust(wit_bindgen_gen_guest_rust::Opts), + Rust { + #[clap(flatten)] + opts: wit_bindgen_gen_guest_rust::Opts, + #[clap(flatten)] + args: Common, + }, /// Generates bindings for C/CPP guest modules. #[cfg(feature = "c")] - C(wit_bindgen_gen_guest_c::Opts), + C { + #[clap(flatten)] + opts: wit_bindgen_gen_guest_c::Opts, + #[clap(flatten)] + args: Common, + }, + /// Generates bindings for TeaVM-based Java guest modules. #[cfg(feature = "teavm-java")] - TeavmJava(wit_bindgen_gen_guest_teavm_java::Opts), + TeavmJava { + #[clap(flatten)] + opts: wit_bindgen_gen_guest_teavm_java::Opts, + #[clap(flatten)] + args: Common, + }, } #[derive(Debug, Parser)] -struct WorldOpt { +struct Common { + /// Where to place output files + #[clap(long = "out-dir")] + out_dir: Option, + /// WIT document to generate bindings for. - #[clap(value_name = "DOCUMENT")] + #[clap(value_name = "DOCUMENT", index = 1)] wit: PathBuf, /// World within the WIT document specified to generate bindings for. @@ -51,46 +66,23 @@ struct WorldOpt { world: Option, } -#[derive(Debug, Parser)] -struct ComponentOpts { - /// Path to the input wasm component to generate bindings for. - component: PathBuf, - - /// Optionally rename the generated bindings instead of inferring the name - /// from the input `component` path. - #[clap(long)] - name: Option, - - #[clap(flatten)] - common: Common, -} - -#[derive(Debug, Parser, Clone)] -struct Common { - /// Where to place output files - #[clap(long = "out-dir")] - out_dir: Option, -} - fn main() -> Result<()> { - let opt: Opt = Opt::parse(); - let mut files = Files::default(); - let generator = match opt.category { - #[cfg(feature = "rust")] - Category::Rust(opts) => opts.build(), + let (generator, opt) = match Opt::parse() { + #[cfg(feature = "markdown")] + Opt::Markdown { opts, args } => (opts.build(), args), #[cfg(feature = "c")] - Category::C(opts) => opts.build(), + Opt::C { opts, args } => (opts.build(), args), + #[cfg(feature = "rust")] + Opt::Rust { opts, args } => (opts.build(), args), #[cfg(feature = "teavm-java")] - Category::TeavmJava(opts) => opts.build(), - #[cfg(feature = "markdown")] - Category::Markdown(opts) => opts.build(), + Opt::TeavmJava { opts, args } => (opts.build(), args), }; - gen_world(generator, opt.world, &mut files)?; + gen_world(generator, &opt, &mut files)?; for (name, contents) in files.iter() { - let dst = match &opt.common.out_dir { + let dst = match &opt.out_dir { Some(path) => path.join(name), None => name.into(), }; @@ -107,7 +99,7 @@ fn main() -> Result<()> { fn gen_world( mut generator: Box, - opts: WorldOpt, + opts: &Common, files: &mut Files, ) -> Result<()> { let mut resolve = Resolve::default(); @@ -154,3 +146,9 @@ fn gen_world( generator.generate(&resolve, world, files); Ok(()) } + +#[test] +fn verify_cli() { + use clap::CommandFactory; + Opt::command().debug_assert() +}