|
| 1 | +use std::{ |
| 2 | + io::{IsTerminal as _, Write as _}, |
| 3 | + path::PathBuf, |
| 4 | +}; |
| 5 | + |
| 6 | +use anyhow::{bail, Context as _, Result}; |
| 7 | +use clap::Args; |
| 8 | +use wac_graph::{CompositionGraph, EncodeOptions, NodeId, PackageId}; |
| 9 | +use wac_types::{Package, SubtypeChecker}; |
| 10 | + |
| 11 | +/// Plugs the exports of any number of 'plug' components into the imports of a 'socket' component. |
| 12 | +#[derive(Args)] |
| 13 | +#[clap(disable_version_flag = true)] |
| 14 | +pub struct PlugCommand { |
| 15 | + /// The path to the plug component. |
| 16 | + /// |
| 17 | + /// More than one plug can be supplied. |
| 18 | + #[clap(long = "plug", value_name = "PLUG_PATH", required = true)] |
| 19 | + pub plugs: Vec<PathBuf>, |
| 20 | + |
| 21 | + /// The path to the socket component |
| 22 | + #[clap(value_name = "SOCKET_PATH", required = true)] |
| 23 | + pub socket: PathBuf, |
| 24 | + |
| 25 | + /// Whether to emit the WebAssembly text format. |
| 26 | + #[clap(long, short = 't')] |
| 27 | + pub wat: bool, |
| 28 | + |
| 29 | + /// The path to write the output to. |
| 30 | + /// |
| 31 | + /// If not specified, the output will be written to stdout. |
| 32 | + #[clap(long, short = 'o')] |
| 33 | + pub output: Option<PathBuf>, |
| 34 | +} |
| 35 | + |
| 36 | +impl PlugCommand { |
| 37 | + /// Executes the command. |
| 38 | + pub async fn exec(&self) -> Result<()> { |
| 39 | + log::debug!("executing plug command"); |
| 40 | + let mut graph = CompositionGraph::new(); |
| 41 | + |
| 42 | + let socket = std::fs::read(&self.socket).with_context(|| { |
| 43 | + format!( |
| 44 | + "failed to read socket component `{path}`", |
| 45 | + path = self.socket.display() |
| 46 | + ) |
| 47 | + })?; |
| 48 | + |
| 49 | + let socket = Package::from_bytes("socket", None, socket, graph.types_mut())?; |
| 50 | + let socket = graph.register_package(socket)?; |
| 51 | + let socket_instantiation = graph.instantiate(socket); |
| 52 | + |
| 53 | + // Collect the plugs by their names |
| 54 | + let mut plugs_by_name = std::collections::HashMap::<_, Vec<_>>::new(); |
| 55 | + for plug in self.plugs.iter() { |
| 56 | + let name = plug |
| 57 | + .file_stem() |
| 58 | + .map(|fs| fs.to_string_lossy()) |
| 59 | + .with_context(|| format!("path to plug '{}' was not a file", plug.display()))?; |
| 60 | + // TODO(rylev): sanitize the name to ensure it's a valid package identifier. |
| 61 | + plugs_by_name.entry(name).or_default().push(plug); |
| 62 | + } |
| 63 | + |
| 64 | + // Plug each plug into the socket. |
| 65 | + for (name, plug_paths) in plugs_by_name { |
| 66 | + for (i, plug_path) in plug_paths.iter().enumerate() { |
| 67 | + let mut name = format!("plug:{name}"); |
| 68 | + // If there's more than one plug with the same name, append an index to the name. |
| 69 | + if plug_paths.len() > 1 { |
| 70 | + use core::fmt::Write; |
| 71 | + write!(&mut name, "{i}").unwrap(); |
| 72 | + } |
| 73 | + plug_into_socket(&name, plug_path, socket, socket_instantiation, &mut graph)?; |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + // Check we've actually done any plugging. |
| 78 | + if graph |
| 79 | + .get_instantiation_arguments(socket_instantiation) |
| 80 | + .next() |
| 81 | + .is_none() |
| 82 | + { |
| 83 | + bail!("the socket component had no matching imports for the plugs that were provided") |
| 84 | + } |
| 85 | + |
| 86 | + // Export all exports from the socket component. |
| 87 | + for name in graph.types()[graph[socket].ty()] |
| 88 | + .exports |
| 89 | + .keys() |
| 90 | + .cloned() |
| 91 | + .collect::<Vec<_>>() |
| 92 | + { |
| 93 | + let export = graph.alias_instance_export(socket_instantiation, &name)?; |
| 94 | + graph.export(export, &name)?; |
| 95 | + } |
| 96 | + |
| 97 | + let binary_output_to_terminal = |
| 98 | + !self.wat && self.output.is_none() && std::io::stdout().is_terminal(); |
| 99 | + if binary_output_to_terminal { |
| 100 | + bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead"); |
| 101 | + } |
| 102 | + |
| 103 | + let mut bytes = graph.encode(EncodeOptions::default())?; |
| 104 | + if self.wat { |
| 105 | + bytes = wasmprinter::print_bytes(&bytes) |
| 106 | + .context("failed to convert binary wasm output to text")? |
| 107 | + .into_bytes(); |
| 108 | + } |
| 109 | + match &self.output { |
| 110 | + Some(path) => { |
| 111 | + std::fs::write(&path, bytes).context(format!( |
| 112 | + "failed to write output file `{path}`", |
| 113 | + path = path.display() |
| 114 | + ))?; |
| 115 | + } |
| 116 | + None => { |
| 117 | + std::io::stdout() |
| 118 | + .write_all(&bytes) |
| 119 | + .context("failed to write to stdout")?; |
| 120 | + |
| 121 | + if self.wat { |
| 122 | + println!(); |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + Ok(()) |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +/// Take the exports of the plug component and plug them into the socket component. |
| 131 | +fn plug_into_socket( |
| 132 | + name: &str, |
| 133 | + plug_path: &std::path::Path, |
| 134 | + socket: PackageId, |
| 135 | + socket_instantiation: NodeId, |
| 136 | + graph: &mut CompositionGraph, |
| 137 | +) -> Result<(), anyhow::Error> { |
| 138 | + let plug = Package::from_file(name, None, plug_path, graph.types_mut())?; |
| 139 | + let plug = graph.register_package(plug)?; |
| 140 | + |
| 141 | + let mut plugs = Vec::new(); |
| 142 | + let mut cache = Default::default(); |
| 143 | + let mut checker = SubtypeChecker::new(&mut cache); |
| 144 | + for (name, plug_ty) in &graph.types()[graph[plug].ty()].exports { |
| 145 | + if let Some(socket_ty) = graph.types()[graph[socket].ty()].imports.get(name) { |
| 146 | + if let Ok(_) = checker.is_subtype(*plug_ty, graph.types(), *socket_ty, graph.types()) { |
| 147 | + plugs.push(name.clone()); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + // Instantiate the plug component |
| 153 | + let mut plug_instantiation = None; |
| 154 | + for plug_name in plugs { |
| 155 | + log::debug!("using export `{plug_name}` for plug"); |
| 156 | + let plug_instantiation = *plug_instantiation.get_or_insert_with(|| graph.instantiate(plug)); |
| 157 | + let export = graph.alias_instance_export(plug_instantiation, &plug_name)?; |
| 158 | + graph.set_instantiation_argument(socket_instantiation, &plug_name, export)?; |
| 159 | + } |
| 160 | + Ok(()) |
| 161 | +} |
0 commit comments