//! The WebAssembly component tool command line interface. #![deny(missing_docs)] use crate::{ decode_interface_component, ComponentEncoder, InterfaceEncoder, InterfacePrinter, StringEncoding, }; use anyhow::{bail, Context, Result}; use clap::Parser; use std::path::{Path, PathBuf}; use wit_parser::Interface; fn parse_named_interface(s: &str) -> Result { let (name, path) = s .split_once('=') .ok_or_else(|| anyhow::anyhow!("expected a value with format `NAME=INTERFACE`"))?; parse_interface(Some(name.to_string()), Path::new(path)) } fn parse_unnamed_interface(s: &str) -> Result { parse_interface(None, Path::new(s)) } fn parse_interface(name: Option, path: &Path) -> Result { if !path.is_file() { bail!("interface file `{}` does not exist", path.display(),); } let mut interface = Interface::parse_file(&path) .with_context(|| format!("failed to parse interface file `{}`", path.display()))?; interface.name = name.unwrap_or_else(|| "".to_string()); Ok(interface) } /// WebAssembly component encoder. /// /// Encodes a WebAssembly component from a core WebAssembly module. #[derive(Debug, Parser)] #[clap(name = "component-encoder", version = env!("CARGO_PKG_VERSION"))] pub struct WitComponentApp { /// The path to an interface definition file the component imports. #[clap(long = "import", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))] pub imports: Vec, /// The path to an interface definition file the component exports. #[clap(long = "export", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))] pub exports: Vec, /// The path of the output WebAssembly component. #[clap(long, short = 'o', value_name = "OUTPUT")] pub output: Option, /// The default interface the component exports. #[clap(long, short = 'i', value_name = "INTERFACE", parse(try_from_str = parse_unnamed_interface))] pub interface: Option, /// Skip validation of the output component. #[clap(long)] pub skip_validation: bool, /// The expected string encoding format for the component. /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. #[clap(long, value_name = "ENCODING")] pub encoding: Option, /// Path to the WebAssembly module to encode. #[clap(index = 1, value_name = "MODULE")] pub module: PathBuf, } impl WitComponentApp { /// Executes the application. pub fn execute(self) -> Result<()> { if !self.module.is_file() { bail!( "module `{}` does not exist as a file", self.module.display() ); } let output = self.output.unwrap_or_else(|| { let mut stem: PathBuf = self.module.file_stem().unwrap().into(); stem.set_extension("wasm"); stem }); let module = wat::parse_file(&self.module) .with_context(|| format!("failed to parse module `{}`", self.module.display()))?; let mut encoder = ComponentEncoder::default() .module(&module) .imports(&self.imports) .exports(&self.exports) .validate(!self.skip_validation); if let Some(interface) = &self.interface { encoder = encoder.interface(interface); } if let Some(encoding) = &self.encoding { encoder = encoder.encoding(*encoding); } let bytes = encoder.encode().with_context(|| { format!( "failed to encode a component from module `{}`", self.module.display() ) })?; std::fs::write(&output, bytes) .with_context(|| format!("failed to write output file `{}`", output.display()))?; println!("encoded component `{}`", output.display()); Ok(()) } } /// WebAssembly interface encoder. /// /// Encodes a WebAssembly interface as a WebAssembly component. #[derive(Debug, Parser)] #[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))] pub struct WitToWasmApp { /// The path of the output WebAssembly component. #[clap(long, short = 'o', value_name = "OUTPUT")] pub output: Option, /// The path to the WebAssembly interface file to encode. #[clap(index = 1, value_name = "INTERFACE")] pub interface: PathBuf, } impl WitToWasmApp { /// Executes the application. pub fn execute(self) -> Result<()> { let output = self.output.unwrap_or_else(|| { let mut stem: PathBuf = self.interface.file_stem().unwrap().into(); stem.set_extension("wasm"); stem }); let interface = parse_interface(None, &self.interface)?; let encoder = InterfaceEncoder::new(&interface).validate(true); let bytes = encoder.encode().with_context(|| { format!( "failed to encode a component from interface `{}`", self.interface.display() ) })?; std::fs::write(&output, bytes) .with_context(|| format!("failed to write output file `{}`", output.display()))?; println!("encoded interface as component `{}`", output.display()); Ok(()) } } /// WebAssembly interface decoder. /// /// Decodes a WebAssembly interface from a WebAssembly component. #[derive(Debug, Parser)] #[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))] pub struct WasmToWitApp { /// The path of the output WebAssembly interface file. #[clap(long, short = 'o', value_name = "OUTPUT")] pub output: Option, /// The path to the WebAssembly component to decode. #[clap(index = 1, value_name = "COMPONENT")] pub component: PathBuf, } impl WasmToWitApp { /// Executes the application. pub fn execute(self) -> Result<()> { let output = self.output.unwrap_or_else(|| { let mut stem: PathBuf = self.component.file_stem().unwrap().into(); stem.set_extension("wit"); stem }); if !self.component.is_file() { bail!( "component `{}` does not exist as a file", self.component.display() ); } let bytes = wat::parse_file(&self.component) .with_context(|| format!("failed to parse component `{}`", self.component.display()))?; let interface = decode_interface_component(&bytes).with_context(|| { format!("failed to decode component `{}`", self.component.display()) })?; let mut printer = InterfacePrinter::default(); std::fs::write(&output, printer.print(&interface)?) .with_context(|| format!("failed to write output file `{}`", output.display()))?; println!("decoded interface to `{}`", output.display()); Ok(()) } }