Skip to content

Commit 3a4eba4

Browse files
authored
Merge pull request #93 from rylev/wac-plug
New `wac plug` command
2 parents 75478f2 + 5ce9583 commit 3a4eba4

3 files changed

Lines changed: 166 additions & 1 deletion

File tree

src/bin/wac.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::Result;
22
use clap::Parser;
33
use owo_colors::{OwoColorize, Stream, Style};
4-
use wac_cli::commands::{EncodeCommand, ParseCommand, ResolveCommand};
4+
use wac_cli::commands::{EncodeCommand, ParseCommand, PlugCommand, ResolveCommand};
55

66
fn version() -> &'static str {
77
option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION"))
@@ -20,6 +20,7 @@ enum Wac {
2020
Parse(ParseCommand),
2121
Resolve(ResolveCommand),
2222
Encode(EncodeCommand),
23+
Plug(PlugCommand),
2324
}
2425

2526
#[tokio::main]
@@ -30,6 +31,7 @@ async fn main() -> Result<()> {
3031
Wac::Parse(cmd) => cmd.exec().await,
3132
Wac::Resolve(cmd) => cmd.exec().await,
3233
Wac::Encode(cmd) => cmd.exec().await,
34+
Wac::Plug(cmd) => cmd.exec().await,
3335
} {
3436
eprintln!(
3537
"{error}: {e:?}",

src/commands.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
33
mod encode;
44
mod parse;
5+
mod plug;
56
mod resolve;
67

78
pub use self::encode::*;
89
pub use self::parse::*;
10+
pub use self::plug::*;
911
pub use self::resolve::*;

src/commands/plug.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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

Comments
 (0)