From eb3dbb16be17139ca9cb6105f1cb03fd788b5bcc Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Wed, 28 Feb 2024 16:10:42 +0100 Subject: [PATCH] add-stub-dependency command --- Cargo.lock | 10 +++ README.md | 19 ++++ wasm-rpc-stubgen/Cargo.toml | 1 + wasm-rpc-stubgen/README.md | 22 ++++- wasm-rpc-stubgen/src/main.rs | 52 ++++++++++- wasm-rpc-stubgen/src/wit.rs | 168 +++++++++++++++++++++++++++++++++-- 6 files changed, 264 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 184c5b2..f2f7191 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1002,6 +1002,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dir-diff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad16bf5f84253b50d6557681c58c3ab67c47c77d39fed9aeb56e947290bd10" +dependencies = [ + "walkdir", +] + [[package]] name = "directories-next" version = "2.0.0" @@ -1493,6 +1502,7 @@ dependencies = [ "cargo-component-core", "cargo_toml", "clap", + "dir-diff", "fs_extra", "golem-wasm-rpc", "heck", diff --git a/README.md b/README.md index 42aeb99..f285fca 100644 --- a/README.md +++ b/README.md @@ -99,3 +99,22 @@ Options: - `wasm-rpc-path-override`: The path to the `wasm-rpc` crate to be used in the generated stub crate. If not specified, the latest version of `wasm-rpc` will be used. It needs to be an **absolute path**. +## Add stub WIT dependency + +```shell +Usage: wasm-rpc-stubgen add-stub-dependency [OPTIONS] --stub-wit-root --dest-wit-root + +Options: + -s, --stub-wit-root + -d, --dest-wit-root + -o, --overwrite + -h, --help Print help + -V, --version Print version +``` + +The command merges a generated RPC stub as a WIT dependency into an other component's WIT root. + +- `stub-wit-root`: The WIT root generated by either `generate` or `build` command +- `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. \ No newline at end of file diff --git a/wasm-rpc-stubgen/Cargo.toml b/wasm-rpc-stubgen/Cargo.toml index 7e26fc1..608a237 100644 --- a/wasm-rpc-stubgen/Cargo.toml +++ b/wasm-rpc-stubgen/Cargo.toml @@ -16,6 +16,7 @@ cargo_toml = "0.19.1" clap = { version = "4.5.0", features = ["derive"] } cargo-component-core = "=0.7.0" cargo-component = "=0.7.0" +dir-diff = "0.3.3" fs_extra = "1.3.0" golem-wasm-rpc = { path = "../wasm-rpc", version = "0.0.0" } heck = "0.4.1" diff --git a/wasm-rpc-stubgen/README.md b/wasm-rpc-stubgen/README.md index ad87bd1..eeed1a8 100644 --- a/wasm-rpc-stubgen/README.md +++ b/wasm-rpc-stubgen/README.md @@ -3,6 +3,7 @@ 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 @@ -36,6 +37,7 @@ target directory's interface via WASM RPC. ## Build + ``` Usage: wasm-rpc-stubgen build [OPTIONS] --source-wit-root --dest-wasm --dest-wit-root @@ -51,7 +53,7 @@ Options: ``` - `source-wit-root`: The root directory of the component's WIT definition to be called via RPC -- `dest-wasm`: The name of the stub WASM file to be generated +- `dest-wasm`: The name of the stub WASM file to be generated - `dest-wit-root`: The directory name where the generated WIT files should be placed - `world`: The world name to be used in the generated stub crate. If there is only a single world in the source root package, no need to specify. @@ -59,4 +61,22 @@ Options: - `wasm-rpc-path-override`: The path to the `wasm-rpc` crate to be used in the generated stub crate. If not specified, the latest version of `wasm-rpc` will be used. It needs to be an **absolute path**. +## Add stub WIT dependency + +```shell +Usage: wasm-rpc-stubgen add-stub-dependency [OPTIONS] --stub-wit-root --dest-wit-root + +Options: + -s, --stub-wit-root + -d, --dest-wit-root + -o, --overwrite + -h, --help Print help + -V, --version Print version +``` + +The command merges a generated RPC stub as a WIT dependency into an other component's WIT root. +- `stub-wit-root`: The WIT root generated by either `generate` or `build` command +- `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. \ No newline at end of file diff --git a/wasm-rpc-stubgen/src/main.rs b/wasm-rpc-stubgen/src/main.rs index 634a1bd..f86e6cf 100644 --- a/wasm-rpc-stubgen/src/main.rs +++ b/wasm-rpc-stubgen/src/main.rs @@ -22,7 +22,7 @@ use crate::cargo::generate_cargo_toml; use crate::compilation::compile; use crate::rust::generate_stub_source; use crate::stub::StubDefinition; -use crate::wit::{copy_wit_files, generate_stub_wit}; +use crate::wit::{copy_wit_files, generate_stub_wit, verify_action, WitAction}; use anyhow::Context; use clap::Parser; use fs_extra::dir::CopyOptions; @@ -37,6 +37,7 @@ use tempdir::TempDir; enum Command { Generate(GenerateArgs), Build(BuildArgs), + AddStubDependency(AddStubDependencyArgs), } #[derive(clap::Args)] @@ -71,6 +72,17 @@ struct BuildArgs { wasm_rpc_path_override: Option, } +#[derive(clap::Args)] +#[command(version, about, long_about = None)] +struct AddStubDependencyArgs { + #[clap(short, long)] + stub_wit_root: PathBuf, + #[clap(short, long)] + dest_wit_root: PathBuf, + #[clap(short, long)] + overwrite: bool, +} + #[tokio::main] async fn main() { match Command::parse() { @@ -80,6 +92,9 @@ async fn main() { Command::Build(build_args) => { let _ = render_error(build(build_args).await); } + Command::AddStubDependency(add_stub_dependency_args) => { + let _ = render_error(add_stub_dependency(add_stub_dependency_args)); + } } } @@ -165,3 +180,38 @@ async fn build(args: BuildArgs) -> anyhow::Result<()> { Ok(()) } + +fn add_stub_dependency(args: AddStubDependencyArgs) -> anyhow::Result<()> { + let source_deps = wit::get_dep_dirs(&args.stub_wit_root)?; + + let main_wit = args.stub_wit_root.join("_stub.wit"); + let main_wit_package_name = wit::get_package_name(&main_wit)?; + + let mut actions = Vec::new(); + for source_dir in source_deps { + actions.push(WitAction::CopyDepDir { source_dir }) + } + actions.push(WitAction::CopyDepWit { + source_wit: main_wit, + dir_name: format!( + "{}_{}", + main_wit_package_name.namespace, main_wit_package_name.name + ), + }); + + let mut proceed = true; + for action in &actions { + if !verify_action(action, &args.dest_wit_root, args.overwrite)? { + eprintln!("Cannot {action} because the destination already exists with a different content. Use --overwrite to force."); + proceed = false; + } + } + + if proceed { + for action in actions { + action.perform(&args.dest_wit_root)?; + } + } + + Ok(()) +} diff --git a/wasm-rpc-stubgen/src/wit.rs b/wasm-rpc-stubgen/src/wit.rs index 0716f47..c3fc8cd 100644 --- a/wasm-rpc-stubgen/src/wit.rs +++ b/wasm-rpc-stubgen/src/wit.rs @@ -13,12 +13,12 @@ // limitations under the License. use crate::stub::{FunctionParamStub, FunctionResultStub, StubDefinition}; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Context}; use indexmap::IndexSet; -use std::fmt::Write; +use std::fmt::{Display, Formatter, Write}; use std::fs; -use std::path::Path; -use wit_parser::{Handle, Resolve, Type, TypeDefKind}; +use std::path::{Path, PathBuf}; +use wit_parser::{Handle, PackageName, Resolve, Type, TypeDefKind, UnresolvedPackage}; pub fn generate_stub_wit(def: &StubDefinition) -> anyhow::Result<()> { let world = def.resolve.worlds.get(def.world_id).unwrap(); @@ -72,7 +72,7 @@ pub fn generate_stub_wit(def: &StubDefinition) -> anyhow::Result<()> { write!(out, ")")?; } FunctionResultStub::SelfType => { - return Err(anyhow!("Unexpected return type in wit generator")) + return Err(anyhow!("Unexpected return type in wit generator")); } } } @@ -94,7 +94,7 @@ pub fn generate_stub_wit(def: &StubDefinition) -> anyhow::Result<()> { write!(out, ")")?; } FunctionResultStub::SelfType => { - return Err(anyhow!("Unexpected return type in wit generator")) + return Err(anyhow!("Unexpected return type in wit generator")); } } } @@ -278,3 +278,159 @@ impl TypeExtensions for Type { } } } + +pub fn get_dep_dirs(wit_root: &Path) -> anyhow::Result> { + let mut result = Vec::new(); + let deps = wit_root.join("deps"); + if deps.exists() && deps.is_dir() { + for entry in fs::read_dir(deps)? { + let entry = entry?; + if entry.file_type()?.is_dir() { + result.push(entry.path()); + } + } + } + Ok(result) +} + +pub fn get_package_name(wit: &Path) -> anyhow::Result { + let pkg = UnresolvedPackage::parse_file(wit)?; + Ok(pkg.name) +} + +pub enum WitAction { + CopyDepDir { + source_dir: PathBuf, + }, + CopyDepWit { + source_wit: PathBuf, + dir_name: String, + }, +} + +impl Display for WitAction { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + WitAction::CopyDepDir { source_dir } => { + write!( + f, + "copy WIT dependency from {}", + source_dir.to_string_lossy() + ) + } + WitAction::CopyDepWit { + source_wit, + dir_name, + } => { + write!( + f, + "copy stub WIT from {} as dependency {}", + source_wit.to_string_lossy(), + dir_name + ) + } + } + } +} + +impl WitAction { + pub fn perform(&self, target_wit_root: &Path) -> anyhow::Result<()> { + match self { + WitAction::CopyDepDir { source_dir } => { + let dep_name = source_dir + .file_name() + .context("Get wit dependency directory name")?; + let target_path = target_wit_root.join("deps").join(dep_name); + if !target_path.exists() { + fs::create_dir_all(&target_path).context("Create target directory")?; + } + println!("Copying {source_dir:?} to {target_path:?}"); + fs_extra::dir::copy( + source_dir, + &target_path, + &fs_extra::dir::CopyOptions::new() + .content_only(true) + .overwrite(true), + ) + .context("Failed to copy the dependency directory")?; + } + WitAction::CopyDepWit { + source_wit, + dir_name, + } => { + let target_dir = target_wit_root.join("deps").join(dir_name); + if !target_dir.exists() { + fs::create_dir_all(&target_dir).context("Create target directory")?; + } + fs::copy(source_wit, target_dir.join(source_wit.file_name().unwrap())) + .context("Copy the WIT file")?; + } + } + + Ok(()) + } +} + +pub fn verify_action( + action: &WitAction, + target_wit_root: &Path, + overwrite: bool, +) -> anyhow::Result { + match action { + WitAction::CopyDepDir { source_dir } => { + let dep_name = source_dir + .file_name() + .context("Get wit dependency directory name")?; + let target_path = target_wit_root.join("deps").join(dep_name); + if target_path.exists() && target_path.is_dir() { + if !dir_diff::is_different(source_dir, &target_path)? { + Ok(true) + } else if overwrite { + println!("Overwriting {}", target_path.to_string_lossy()); + Ok(true) + } else { + Ok(false) + } + } else { + Ok(true) + } + } + WitAction::CopyDepWit { + source_wit, + dir_name, + } => { + let target_dir = target_wit_root.join("deps").join(dir_name); + let source_file_name = source_wit.file_name().context("Get source wit file name")?; + let target_wit = target_dir.join(source_file_name); + if target_dir.exists() && target_dir.is_dir() { + let mut existing_entries = Vec::new(); + for entry in fs::read_dir(&target_dir)? { + let entry = entry?; + let name = entry + .path() + .file_name() + .context("Get existing wit directory's name")? + .to_string_lossy() + .to_string(); + existing_entries.push(name); + } + if existing_entries.contains(&source_file_name.to_string_lossy().to_string()) { + let source_contents = fs::read_to_string(source_wit)?; + let target_contents = fs::read_to_string(&target_wit)?; + if source_contents == target_contents { + Ok(true) + } else if overwrite { + println!("Overwriting {}", target_wit.to_string_lossy()); + Ok(true) + } else { + Ok(false) + } + } else { + Ok(true) + } + } else { + Ok(true) + } + } + } +}