diff --git a/Cargo.lock b/Cargo.lock index f2f71917..cf8ee8ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,6 +424,15 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1464,9 +1473,9 @@ dependencies = [ [[package]] name = "golem-wasm-ast" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffc8f75ae9167c79cb48cef72c3d8bdc2e4d58f397a670866e6db95bdc2bc59" +checksum = "d9ed9390768d544789a08504531c032f355d4a1ba70a41e984f1f5266c91224d" dependencies = [ "leb128", "mappable-rc", @@ -1504,10 +1513,12 @@ dependencies = [ "clap", "dir-diff", "fs_extra", + "golem-wasm-ast", "golem-wasm-rpc", "heck", "id-arena", "indexmap 2.2.3", + "pretty_env_logger", "prettyplease 0.2.16", "proc-macro2", "quote", @@ -1516,8 +1527,9 @@ dependencies = [ "tempdir", "tokio", "toml 0.8.10", + "wasm-compose", "wit-bindgen-rust 0.17.0", - "wit-parser 0.14.0", + "wit-parser 0.201.0", ] [[package]] @@ -1744,6 +1756,20 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2837,6 +2863,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rayon" version = "1.8.1" @@ -3273,6 +3308,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap 2.2.3", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3314,6 +3362,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.9" @@ -3788,6 +3846,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "url" version = "2.5.0" @@ -4060,6 +4124,27 @@ version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +[[package]] +name = "wasm-compose" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10501f9526fedb9592271a19c34257582b6abcb4636e276ee75cb95aa242ee2a" +dependencies = [ + "anyhow", + "heck", + "im-rc", + "indexmap 2.2.3", + "log", + "petgraph", + "serde", + "serde_derive", + "serde_yaml", + "smallvec", + "wasm-encoder 0.201.0", + "wasmparser 0.201.0", + "wat", +] + [[package]] name = "wasm-encoder" version = "0.38.1" @@ -4078,6 +4163,16 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" +dependencies = [ + "leb128", + "wasmparser 0.201.0", +] + [[package]] name = "wasm-metadata" version = "0.10.20" @@ -4138,6 +4233,17 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" +dependencies = [ + "bitflags 2.4.2", + "indexmap 2.2.3", + "semver", +] + [[package]] name = "wasmprinter" version = "0.2.80" @@ -4457,22 +4563,22 @@ checksum = "b20a19e10d8cb50b45412fb21192982b7ce85c0122dc33bb71f1813e25dc6e52" [[package]] name = "wast" -version = "71.0.1" +version = "201.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "647c3ac4354da32688537e8fc4d2fe6c578df51896298cb64727d98088a1fd26" +checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.41.2", + "wasm-encoder 0.201.0", ] [[package]] name = "wat" -version = "1.0.88" +version = "1.201.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69c36f634411568a2c6d24828b674961e37ea03340fe1d605c337ed8162d901" +checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32" dependencies = [ "wast", ] @@ -4842,9 +4948,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.14.0" +version = "0.201.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee4ad7310367bf272507c0c8e0c74a80b4ed586b833f7c7ca0b7588f686f11a" +checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", @@ -4855,7 +4961,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.121.2", + "wasmparser 0.201.0", ] [[package]] diff --git a/README.md b/README.md index c5a99cbd..ab2b0813 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,15 @@ Defines data types for [Golem](https://golem.cloud)'s remote function invocation - `WitValue` is the WIT-defined generic data type capable of representing an arbitrary value, generated by `wit-bindgen` - A builder and an extractor API for `WitValue` -- `Value` is a recursive Rust type which is more convenient to work with than `WitValue`. Conversion between `WitValue` and `Value` is implemented in both directions. -- Protobuf message types for describing values and types, and a protobuf version of `WitValue` itself and conversion from and to `Value` and `WitValue` +- `Value` is a recursive Rust type which is more convenient to work with than `WitValue`. Conversion between `WitValue` + and `Value` is implemented in both directions. +- Protobuf message types for describing values and types, and a protobuf version of `WitValue` itself and conversion + from and to `Value` and `WitValue` - JSON representation of WIT values, as defined in [the Golem docs](https://learn.golem.cloud/docs/template-interface). - Conversion of `Value` to and from `wasmtime` values -The JSON representation requires additional type information which can be extracted using the [golem-wasm-ast](https://crates.io/crates/golem-wasm-ast) crate. +The JSON representation requires additional type information which can be extracted using +the [golem-wasm-ast](https://crates.io/crates/golem-wasm-ast) crate. ## Host and stub mode @@ -30,19 +33,21 @@ cargo component build -p wasm-rpc --no-default-features --features stub ``` ## Feature flags + - `arbitrary` adds an `Arbitrary` instance for `Value` - `json` adds conversion functions for mapping of a WIT value and type definition to/from JSON - `protobuf` adds the protobuf message types - `wasmtime` adds conversion to `wasmtime` `Val` values - `host` enables all features: `arbitrary`, `json`, `protobuf`, `typeinfo`, and `wasmtime` -- `stub` is to be used in generated WASM stubs and disables all features, and generates guest bindings instead of host bindings - +- `stub` is to be used in generated WASM stubs and disables all features, and generates guest bindings instead of host + bindings ## golem-wasm-rpc-stubgen The `golem-wasm-rpc-stubgen` is a CLI tool to generate the RPC stubs from a component's WIT definition. ## Generate + ```shell Usage: wasm-rpc-stubgen generate [OPTIONS] --source-wit-root --dest-crate-root @@ -76,6 +81,7 @@ target directory's interface via WASM RPC. ## Build + ``` Usage: wasm-rpc-stubgen build [OPTIONS] --source-wit-root --dest-wasm --dest-wit-root @@ -119,4 +125,25 @@ The command merges a generated RPC stub as a WIT dependency into an other compon - `dest-wit-root`: The WIT root of the component where the stub should be added as a dependency - `overwrite`: This command would not do anything if it detects that it would change an existing WIT file's contents at the destination. With this flag, it can be forced to overwrite those files. -- `update-cargo-toml`: Enables updating the Cargo.toml file in the parent directory of `dest-wit-root` with the copied dependencies.ation. With this flag, it can be forced to overwrite those files. \ No newline at end of file +- `update-cargo-toml`: Enables updating the Cargo.toml file in the parent directory of `dest-wit-root` with the copied + dependencies. + +## Compose the stub with the caller component + +```shell +Usage: wasm-rpc-stubgen compose --source-wasm --stub-wasm --dest-wasm + +Options: + --source-wasm + --stub-wasm + --dest-wasm + -h, --help Print help + -V, --version Print version +``` + +The command composes a caller component's WASM (which uses the generated stub to call a remote worker) with the +generated stub WASM, writing out a composed WASM which no longer depends on the stub interface, ready to use. + +- `source-wasm`: The WASM file of the caller component +- `stub-wasm`: The WASM file of the generated stub +- `dest-wasm`: The name of the composed WASM file to be generated diff --git a/wasm-rpc-stubgen/Cargo.toml b/wasm-rpc-stubgen/Cargo.toml index 608a237d..7ba3d4d9 100644 --- a/wasm-rpc-stubgen/Cargo.toml +++ b/wasm-rpc-stubgen/Cargo.toml @@ -18,10 +18,12 @@ cargo-component-core = "=0.7.0" cargo-component = "=0.7.0" dir-diff = "0.3.3" fs_extra = "1.3.0" +golem-wasm-ast = "0.2.1" golem-wasm-rpc = { path = "../wasm-rpc", version = "0.0.0" } heck = "0.4.1" id-arena = "2.2.1" indexmap = "2.0.0" +pretty_env_logger = "0.5.0" prettyplease = "0.2.16" proc-macro2 = "1.0.78" quote = "1.0.35" @@ -30,5 +32,6 @@ syn = "2.0.48" tempdir = "0.3.7" tokio = "1.36.0" toml = "0.8.10" +wasm-compose = "0.201.0" wit-bindgen-rust = "=0.17.0" -wit-parser = "0.14.0" +wit-parser = "0.201.0" diff --git a/wasm-rpc-stubgen/README.md b/wasm-rpc-stubgen/README.md index 5e990317..28326faa 100644 --- a/wasm-rpc-stubgen/README.md +++ b/wasm-rpc-stubgen/README.md @@ -81,4 +81,24 @@ The command merges a generated RPC stub as a WIT dependency into an other compon - `dest-wit-root`: The WIT root of the component where the stub should be added as a dependency - `overwrite`: This command would not do anything if it detects that it would change an existing WIT file's contents at the destination. With this flag, it can be forced to overwrite those files. -- `update-cargo-toml`: Enables updating the Cargo.toml file in the parent directory of `dest-wit-root` with the copied dependencies. \ No newline at end of file +- `update-cargo-toml`: Enables updating the Cargo.toml file in the parent directory of `dest-wit-root` with the copied dependencies. +- +## Compose the stub with the caller component + +```shell +Usage: wasm-rpc-stubgen compose --source-wasm --stub-wasm --dest-wasm + +Options: + --source-wasm + --stub-wasm + --dest-wasm + -h, --help Print help + -V, --version Print version +``` + +The command composes a caller component's WASM (which uses the generated stub to call a remote worker) with the +generated stub WASM, writing out a composed WASM which no longer depends on the stub interface, ready to use. + +- `source-wasm`: The WASM file of the caller component +- `stub-wasm`: The WASM file of the generated stub +- `dest-wasm`: The name of the composed WASM file to be generated diff --git a/wasm-rpc-stubgen/src/main.rs b/wasm-rpc-stubgen/src/main.rs index b7ebbb5d..c077eb70 100644 --- a/wasm-rpc-stubgen/src/main.rs +++ b/wasm-rpc-stubgen/src/main.rs @@ -26,10 +26,14 @@ use crate::wit::{copy_wit_files, generate_stub_wit, verify_action, WitAction}; use anyhow::{anyhow, Context}; use clap::Parser; use fs_extra::dir::CopyOptions; +use golem_wasm_ast::analysis::{AnalysedExport, AnalysisContext, AnalysisFailure}; +use golem_wasm_ast::component::Component; +use golem_wasm_ast::IgnoreAllButMetadata; use heck::ToSnakeCase; use std::fs; use std::path::PathBuf; use tempdir::TempDir; +use wasm_compose::config::Dependency; #[derive(Parser)] #[command(name = "wasm-rpc-stubgen")] @@ -38,6 +42,7 @@ enum Command { Generate(GenerateArgs), Build(BuildArgs), AddStubDependency(AddStubDependencyArgs), + Compose(ComposeArgs), } #[derive(clap::Args)] @@ -85,8 +90,21 @@ struct AddStubDependencyArgs { update_cargo_toml: bool, } +#[derive(clap::Args)] +#[command(version, about, long_about = None)] +struct ComposeArgs { + #[clap(long)] + source_wasm: PathBuf, + #[clap(long)] + stub_wasm: PathBuf, + #[clap(long)] + dest_wasm: PathBuf, +} + #[tokio::main] async fn main() { + pretty_env_logger::init(); + match Command::parse() { Command::Generate(generate_args) => { let _ = render_error(generate(generate_args)); @@ -97,6 +115,9 @@ async fn main() { Command::AddStubDependency(add_stub_dependency_args) => { let _ = render_error(add_stub_dependency(add_stub_dependency_args)); } + Command::Compose(compose_args) => { + let _ = render_error(compose(compose_args)); + } } } @@ -242,3 +263,33 @@ fn add_stub_dependency(args: AddStubDependencyArgs) -> anyhow::Result<()> { Ok(()) } + +fn compose(args: ComposeArgs) -> anyhow::Result<()> { + let mut config = wasm_compose::config::Config::default(); + + let stub_bytes = fs::read(&args.stub_wasm)?; + let stub_component = + Component::::from_bytes(&stub_bytes).map_err(|err| anyhow!(err))?; + + let state = AnalysisContext::new(stub_component); + let stub_exports = state.get_top_level_exports().map_err(|err| match err { + AnalysisFailure::Failed(msg) => anyhow!(msg), + })?; + + for export in stub_exports { + if let AnalysedExport::Instance(instance) = export { + config.dependencies.insert( + instance.name.clone(), + Dependency { + path: args.stub_wasm.clone(), + }, + ); + } + } + + let composer = wasm_compose::composer::ComponentComposer::new(&args.source_wasm, &config); + let result = composer.compose()?; + println!("Writing composed component to {:?}", args.dest_wasm); + fs::write(&args.dest_wasm, result).context("Failed to write the composed component")?; + Ok(()) +}