wac_cli/commands/
compose.rs

1use crate::{fmt_err, PackageResolver};
2use anyhow::{bail, Context, Result};
3use clap::Args;
4use std::{
5    fs,
6    io::{IsTerminal, Write},
7    path::PathBuf,
8};
9use wac_graph::EncodeOptions;
10use wac_parser::Document;
11use wasmprinter::print_bytes;
12
13fn parse<T, U>(s: &str) -> Result<(T, U)>
14where
15    T: std::str::FromStr,
16    T::Err: Into<anyhow::Error>,
17    U: std::str::FromStr,
18    U::Err: Into<anyhow::Error>,
19{
20    let (k, v) = s.split_once('=').context("value does not contain `=`")?;
21
22    Ok((
23        k.trim().parse().map_err(Into::into)?,
24        v.trim().parse().map_err(Into::into)?,
25    ))
26}
27
28/// Compose WebAssembly components using the provided WAC source file.
29#[derive(Args)]
30#[clap(disable_version_flag = true)]
31pub struct ComposeCommand {
32    /// The directory to search for package dependencies.
33    #[clap(long, value_name = "PATH", default_value = "deps")]
34    pub deps_dir: PathBuf,
35
36    /// The directory to search for package dependencies.
37    #[clap(long = "dep", short, value_name = "PKG=PATH", value_parser = parse::<String, PathBuf>)]
38    pub deps: Vec<(String, PathBuf)>,
39
40    /// Whether to skip validation of the composed WebAssembly component.
41    #[clap(long)]
42    pub no_validate: bool,
43
44    /// Whether to emit the WebAssembly text format.
45    #[clap(long, short = 't')]
46    pub wat: bool,
47
48    /// Whether the composed component imports its dependencies.
49    ///
50    /// If false, all referenced dependent packages will be defined within the component.
51    ///
52    /// Defaults to false.
53    #[clap(long, short)]
54    pub import_dependencies: bool,
55
56    /// The path to write the output to.
57    ///
58    /// If not specified, the output will be written to stdout.
59    #[clap(long, short = 'o')]
60    pub output: Option<PathBuf>,
61
62    /// The URL of the registry to use.
63    #[cfg(feature = "registry")]
64    #[clap(long, value_name = "URL")]
65    pub registry: Option<String>,
66
67    /// The path to the source WAC file.
68    #[clap(value_name = "PATH")]
69    pub path: PathBuf,
70}
71
72impl ComposeCommand {
73    /// Executes the command.
74    pub async fn exec(self) -> Result<()> {
75        log::debug!("executing compose command");
76
77        let contents = fs::read_to_string(&self.path)
78            .with_context(|| format!("failed to read file `{path}`", path = self.path.display()))?;
79
80        let document = Document::parse(&contents).map_err(|e| fmt_err(e, &self.path, &contents))?;
81
82        let mut resolver = PackageResolver::new(
83            self.deps_dir,
84            self.deps.into_iter().collect(),
85            #[cfg(feature = "registry")]
86            self.registry.as_deref(),
87        )
88        .await?;
89
90        let packages = resolver
91            .resolve(&document)
92            .await
93            .map_err(|e| fmt_err(e, &self.path, &contents))?;
94
95        let resolution = document
96            .resolve(packages)
97            .map_err(|e| fmt_err(e, &self.path, &contents))?;
98
99        if !self.wat && self.output.is_none() && std::io::stdout().is_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 = resolution.encode(EncodeOptions {
104            define_components: !self.import_dependencies,
105            validate: !self.no_validate,
106            ..Default::default()
107        })?;
108        if self.wat {
109            bytes = print_bytes(&bytes)
110                .context("failed to convert binary wasm output to text")?
111                .into_bytes();
112        }
113
114        match self.output {
115            Some(path) => {
116                fs::write(&path, bytes).context(format!(
117                    "failed to write output file `{path}`",
118                    path = path.display()
119                ))?;
120            }
121            None => {
122                std::io::stdout()
123                    .write_all(&bytes)
124                    .context("failed to write to stdout")?;
125
126                if self.wat {
127                    println!();
128                }
129            }
130        }
131
132        Ok(())
133    }
134}