From 557a430efcce60021e1664dec9f6903a70446088 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Fri, 19 Apr 2024 17:31:20 +0200 Subject: [PATCH 1/4] Introduce a new command 'plug' The goal of the command is to treat one component as a plug which will have all of its exports that match the imports of another component, the socket, plugged into the socket component. The exports of the socket will be re-exported. --- src/bin/wac.rs | 4 +- src/commands.rs | 2 + src/commands/plug.rs | 95 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/commands/plug.rs diff --git a/src/bin/wac.rs b/src/bin/wac.rs index 73abeaa2..b7963180 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::Parser; use owo_colors::{OwoColorize, Stream, Style}; -use wac_cli::commands::{EncodeCommand, ParseCommand, ResolveCommand}; +use wac_cli::commands::{EncodeCommand, ParseCommand, PlugCommand, ResolveCommand}; fn version() -> &'static str { option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) @@ -20,6 +20,7 @@ enum Wac { Parse(ParseCommand), Resolve(ResolveCommand), Encode(EncodeCommand), + Plug(PlugCommand), } #[tokio::main] @@ -30,6 +31,7 @@ async fn main() -> Result<()> { Wac::Parse(cmd) => cmd.exec().await, Wac::Resolve(cmd) => cmd.exec().await, Wac::Encode(cmd) => cmd.exec().await, + Wac::Plug(cmd) => cmd.exec().await, } { eprintln!( "{error}: {e:?}", diff --git a/src/commands.rs b/src/commands.rs index acd03ac7..00e02863 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,8 +2,10 @@ mod encode; mod parse; +mod plug; mod resolve; pub use self::encode::*; pub use self::parse::*; +pub use self::plug::*; pub use self::resolve::*; diff --git a/src/commands/plug.rs b/src/commands/plug.rs new file mode 100644 index 00000000..4ca09591 --- /dev/null +++ b/src/commands/plug.rs @@ -0,0 +1,95 @@ +use std::{io::Write as _, path::PathBuf}; + +use anyhow::{Context as _, Result}; +use clap::Args; +use wac_graph::{CompositionGraph, EncodeOptions}; +use wac_types::{Package, SubtypeChecker}; + +/// Plugs the exports of any number of 'plug' components into the imports of a 'socket' component. +#[derive(Args)] +#[clap(disable_version_flag = true)] +pub struct PlugCommand { + /// The path to the plug component + pub plug: PathBuf, + + /// The path to the socket component + pub socket: PathBuf, + + /// The path to write the output to. + /// + /// If not specified, the output will be written to stdout. + #[clap(long, short = 'o')] + pub output: Option, +} + +impl PlugCommand { + /// Executes the command. + pub async fn exec(&self) -> Result<()> { + log::debug!("executing plug command"); + let mut graph = CompositionGraph::new(); + + let plug = std::fs::read(&self.plug).with_context(|| { + format!( + "failed to read plug component `{path}`", + path = self.plug.display() + ) + })?; + let socket = std::fs::read(&self.socket).with_context(|| { + format!( + "failed to read socket component `{path}`", + path = self.plug.display() + ) + })?; + + // Register the packages + let plug = Package::from_bytes("plug", None, plug, graph.types_mut())?; + let plug = graph.register_package(plug)?; + let socket = Package::from_bytes("socket", None, socket, graph.types_mut())?; + let socket = graph.register_package(socket)?; + + let socket_instantiation = graph.instantiate(socket); + let plug_instantiation = graph.instantiate(plug); + let mut plugs = Vec::new(); + let mut cache = Default::default(); + let mut checker = SubtypeChecker::new(&mut cache); + for (name, plug_ty) in &graph.types()[graph[plug].ty()].exports { + if let Some(socket_ty) = graph.types()[graph[socket].ty()].imports.get(name) { + if let Ok(_) = + checker.is_subtype(*plug_ty, graph.types(), *socket_ty, graph.types()) + { + plugs.push(name.clone()); + } + } + } + for plug in plugs { + log::debug!("using export `{plug}` for plug"); + let export = graph.alias_instance_export(plug_instantiation, &plug)?; + graph.set_instantiation_argument(socket_instantiation, &plug, export)?; + } + for name in graph.types()[graph[socket].ty()] + .exports + .keys() + .cloned() + .collect::>() + { + let export = graph.alias_instance_export(socket_instantiation, &name)?; + graph.export(export, &name)?; + } + + let bytes = graph.encode(EncodeOptions::default())?; + match &self.output { + Some(path) => { + std::fs::write(&path, bytes).context(format!( + "failed to write output file `{path}`", + path = path.display() + ))?; + } + None => { + std::io::stdout() + .write_all(&bytes) + .context("failed to write to stdout")?; + } + } + Ok(()) + } +} From d38fd96e867055c6be22abb9030ed4dcb37e1068 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Fri, 19 Apr 2024 17:48:22 +0200 Subject: [PATCH 2/4] Allow multiple plugs Signed-off-by: Ryan Levick --- src/commands/plug.rs | 88 ++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/src/commands/plug.rs b/src/commands/plug.rs index 4ca09591..16ba28c9 100644 --- a/src/commands/plug.rs +++ b/src/commands/plug.rs @@ -2,17 +2,21 @@ use std::{io::Write as _, path::PathBuf}; use anyhow::{Context as _, Result}; use clap::Args; -use wac_graph::{CompositionGraph, EncodeOptions}; +use wac_graph::{CompositionGraph, EncodeOptions, NodeId, PackageId}; use wac_types::{Package, SubtypeChecker}; /// Plugs the exports of any number of 'plug' components into the imports of a 'socket' component. #[derive(Args)] #[clap(disable_version_flag = true)] pub struct PlugCommand { - /// The path to the plug component - pub plug: PathBuf, + /// The path to the plug component. + /// + /// More than one plug can be supplied. + #[clap(long = "plug", value_name = "PLUG_PATH", required = true)] + pub plugs: Vec, /// The path to the socket component + #[clap(value_name = "SOCKET_PATH", required = true)] pub socket: PathBuf, /// The path to write the output to. @@ -28,44 +32,35 @@ impl PlugCommand { log::debug!("executing plug command"); let mut graph = CompositionGraph::new(); - let plug = std::fs::read(&self.plug).with_context(|| { - format!( - "failed to read plug component `{path}`", - path = self.plug.display() - ) - })?; let socket = std::fs::read(&self.socket).with_context(|| { format!( "failed to read socket component `{path}`", - path = self.plug.display() + path = self.socket.display() ) })?; - // Register the packages - let plug = Package::from_bytes("plug", None, plug, graph.types_mut())?; - let plug = graph.register_package(plug)?; let socket = Package::from_bytes("socket", None, socket, graph.types_mut())?; let socket = graph.register_package(socket)?; - let socket_instantiation = graph.instantiate(socket); - let plug_instantiation = graph.instantiate(plug); - let mut plugs = Vec::new(); - let mut cache = Default::default(); - let mut checker = SubtypeChecker::new(&mut cache); - for (name, plug_ty) in &graph.types()[graph[plug].ty()].exports { - if let Some(socket_ty) = graph.types()[graph[socket].ty()].imports.get(name) { - if let Ok(_) = - checker.is_subtype(*plug_ty, graph.types(), *socket_ty, graph.types()) - { - plugs.push(name.clone()); - } - } - } - for plug in plugs { - log::debug!("using export `{plug}` for plug"); - let export = graph.alias_instance_export(plug_instantiation, &plug)?; - graph.set_instantiation_argument(socket_instantiation, &plug, export)?; + + // Plug each plug into the socket. + for (i, plug) in self.plugs.iter().enumerate() { + let plug = std::fs::read(&plug).with_context(|| { + format!( + "failed to read plug component `{path}`", + path = plug.display() + ) + })?; + plug_into_socket( + &format!("plug{i}"), + plug, + socket, + socket_instantiation, + &mut graph, + )?; } + + // Export all exports from the socket component. for name in graph.types()[graph[socket].ty()] .exports .keys() @@ -93,3 +88,34 @@ impl PlugCommand { Ok(()) } } + +/// Take the exports of the plug component and plug them into the socket component. +fn plug_into_socket( + name: &str, + plug: Vec, + socket: PackageId, + socket_instantiation: NodeId, + graph: &mut CompositionGraph, +) -> Result<(), anyhow::Error> { + let plug = Package::from_bytes(name, None, plug, graph.types_mut())?; + let plug = graph.register_package(plug)?; + let plug_instantiation = graph.instantiate(plug); + + let mut plugs = Vec::new(); + let mut cache = Default::default(); + let mut checker = SubtypeChecker::new(&mut cache); + for (name, plug_ty) in &graph.types()[graph[plug].ty()].exports { + if let Some(socket_ty) = graph.types()[graph[socket].ty()].imports.get(name) { + if let Ok(_) = checker.is_subtype(*plug_ty, graph.types(), *socket_ty, graph.types()) { + plugs.push(name.clone()); + } + } + } + + for plug in plugs { + log::debug!("using export `{plug}` for plug"); + let export = graph.alias_instance_export(plug_instantiation, &plug)?; + graph.set_instantiation_argument(socket_instantiation, &plug, export)?; + } + Ok(()) +} From 7223739390be16735954f58b0af276ff63a35724 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Wed, 24 Apr 2024 14:25:32 +0200 Subject: [PATCH 3/4] Address feedback Signed-off-by: Ryan Levick --- src/commands/plug.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/commands/plug.rs b/src/commands/plug.rs index 16ba28c9..23d40945 100644 --- a/src/commands/plug.rs +++ b/src/commands/plug.rs @@ -1,6 +1,6 @@ use std::{io::Write as _, path::PathBuf}; -use anyhow::{Context as _, Result}; +use anyhow::{bail, Context as _, Result}; use clap::Args; use wac_graph::{CompositionGraph, EncodeOptions, NodeId, PackageId}; use wac_types::{Package, SubtypeChecker}; @@ -60,6 +60,15 @@ impl PlugCommand { )?; } + // Check we've actually done any plugging. + if graph + .get_instantiation_arguments(socket_instantiation) + .next() + .is_none() + { + bail!("no plugs were used to plug into the socket component") + } + // Export all exports from the socket component. for name in graph.types()[graph[socket].ty()] .exports @@ -99,7 +108,6 @@ fn plug_into_socket( ) -> Result<(), anyhow::Error> { let plug = Package::from_bytes(name, None, plug, graph.types_mut())?; let plug = graph.register_package(plug)?; - let plug_instantiation = graph.instantiate(plug); let mut plugs = Vec::new(); let mut cache = Default::default(); @@ -112,10 +120,13 @@ fn plug_into_socket( } } - for plug in plugs { - log::debug!("using export `{plug}` for plug"); - let export = graph.alias_instance_export(plug_instantiation, &plug)?; - graph.set_instantiation_argument(socket_instantiation, &plug, export)?; + // Instantiate the plug component + let mut plug_instantiation = None; + for plug_name in plugs { + log::debug!("using export `{plug_name}` for plug"); + let plug_instantiation = *plug_instantiation.get_or_insert_with(|| graph.instantiate(plug)); + let export = graph.alias_instance_export(plug_instantiation, &plug_name)?; + graph.set_instantiation_argument(socket_instantiation, &plug_name, export)?; } Ok(()) } From 5ce9583575e9c09847ca75a6d148def4d6ac36e7 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Fri, 26 Apr 2024 13:47:23 +0200 Subject: [PATCH 4/4] Address PR feedback Signed-off-by: Ryan Levick --- src/commands/plug.rs | 67 +++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/commands/plug.rs b/src/commands/plug.rs index 23d40945..41198dc9 100644 --- a/src/commands/plug.rs +++ b/src/commands/plug.rs @@ -1,4 +1,7 @@ -use std::{io::Write as _, path::PathBuf}; +use std::{ + io::{IsTerminal as _, Write as _}, + path::PathBuf, +}; use anyhow::{bail, Context as _, Result}; use clap::Args; @@ -19,6 +22,10 @@ pub struct PlugCommand { #[clap(value_name = "SOCKET_PATH", required = true)] pub socket: PathBuf, + /// Whether to emit the WebAssembly text format. + #[clap(long, short = 't')] + pub wat: bool, + /// The path to write the output to. /// /// If not specified, the output will be written to stdout. @@ -43,21 +50,28 @@ impl PlugCommand { let socket = graph.register_package(socket)?; let socket_instantiation = graph.instantiate(socket); + // Collect the plugs by their names + let mut plugs_by_name = std::collections::HashMap::<_, Vec<_>>::new(); + for plug in self.plugs.iter() { + let name = plug + .file_stem() + .map(|fs| fs.to_string_lossy()) + .with_context(|| format!("path to plug '{}' was not a file", plug.display()))?; + // TODO(rylev): sanitize the name to ensure it's a valid package identifier. + plugs_by_name.entry(name).or_default().push(plug); + } + // Plug each plug into the socket. - for (i, plug) in self.plugs.iter().enumerate() { - let plug = std::fs::read(&plug).with_context(|| { - format!( - "failed to read plug component `{path}`", - path = plug.display() - ) - })?; - plug_into_socket( - &format!("plug{i}"), - plug, - socket, - socket_instantiation, - &mut graph, - )?; + for (name, plug_paths) in plugs_by_name { + for (i, plug_path) in plug_paths.iter().enumerate() { + let mut name = format!("plug:{name}"); + // If there's more than one plug with the same name, append an index to the name. + if plug_paths.len() > 1 { + use core::fmt::Write; + write!(&mut name, "{i}").unwrap(); + } + plug_into_socket(&name, plug_path, socket, socket_instantiation, &mut graph)?; + } } // Check we've actually done any plugging. @@ -66,7 +80,7 @@ impl PlugCommand { .next() .is_none() { - bail!("no plugs were used to plug into the socket component") + bail!("the socket component had no matching imports for the plugs that were provided") } // Export all exports from the socket component. @@ -80,7 +94,18 @@ impl PlugCommand { graph.export(export, &name)?; } - let bytes = graph.encode(EncodeOptions::default())?; + let binary_output_to_terminal = + !self.wat && self.output.is_none() && std::io::stdout().is_terminal(); + if binary_output_to_terminal { + bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead"); + } + + let mut bytes = graph.encode(EncodeOptions::default())?; + if self.wat { + bytes = wasmprinter::print_bytes(&bytes) + .context("failed to convert binary wasm output to text")? + .into_bytes(); + } match &self.output { Some(path) => { std::fs::write(&path, bytes).context(format!( @@ -92,6 +117,10 @@ impl PlugCommand { std::io::stdout() .write_all(&bytes) .context("failed to write to stdout")?; + + if self.wat { + println!(); + } } } Ok(()) @@ -101,12 +130,12 @@ impl PlugCommand { /// Take the exports of the plug component and plug them into the socket component. fn plug_into_socket( name: &str, - plug: Vec, + plug_path: &std::path::Path, socket: PackageId, socket_instantiation: NodeId, graph: &mut CompositionGraph, ) -> Result<(), anyhow::Error> { - let plug = Package::from_bytes(name, None, plug, graph.types_mut())?; + let plug = Package::from_file(name, None, plug_path, graph.types_mut())?; let plug = graph.register_package(plug)?; let mut plugs = Vec::new();