Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GOL-224 add-stub-dependency command #7

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <STUB_WIT_ROOT> --dest-wit-root <DEST_WIT_ROOT>

Options:
-s, --stub-wit-root <STUB_WIT_ROOT>
-d, --dest-wit-root <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.
1 change: 1 addition & 0 deletions wasm-rpc-stubgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 21 additions & 1 deletion wasm-rpc-stubgen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <SOURCE_WIT_ROOT> --dest-crate-root <DEST_CRATE_ROOT>

Expand Down Expand Up @@ -36,6 +37,7 @@ target directory's
interface via WASM RPC.

## Build

```
Usage: wasm-rpc-stubgen build [OPTIONS] --source-wit-root <SOURCE_WIT_ROOT> --dest-wasm <DEST_WASM> --dest-wit-root <DEST_WIT_ROOT>

Expand All @@ -51,12 +53,30 @@ 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.
- `stub-crate-version`: The crate version of the generated stub crate
- `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 <STUB_WIT_ROOT> --dest-wit-root <DEST_WIT_ROOT>

Options:
-s, --stub-wit-root <STUB_WIT_ROOT>
-d, --dest-wit-root <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.
52 changes: 51 additions & 1 deletion wasm-rpc-stubgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +37,7 @@ use tempdir::TempDir;
enum Command {
Generate(GenerateArgs),
Build(BuildArgs),
AddStubDependency(AddStubDependencyArgs),
}

#[derive(clap::Args)]
Expand Down Expand Up @@ -71,6 +72,17 @@ struct BuildArgs {
wasm_rpc_path_override: Option<String>,
}

#[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() {
Expand All @@ -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));
}
}
}

Expand Down Expand Up @@ -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(())
}
168 changes: 162 additions & 6 deletions wasm-rpc-stubgen/src/wit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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"));
}
}
}
Expand All @@ -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"));
}
}
}
Expand Down Expand Up @@ -278,3 +278,159 @@ impl TypeExtensions for Type {
}
}
}

pub fn get_dep_dirs(wit_root: &Path) -> anyhow::Result<Vec<PathBuf>> {
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<PackageName> {
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<bool> {
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)
}
}
}
}
Loading