wasm_tools/
lib.rs

1//! Shared input/output routines amongst most `wasm-tools` subcommands
2
3use anyhow::{bail, Context, Result};
4use std::fs::File;
5use std::io::IsTerminal;
6use std::io::{BufWriter, Read, Write};
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9use termcolor::{Ansi, ColorChoice, NoColor, StandardStream, WriteColor};
10
11#[cfg(any(feature = "addr2line", feature = "validate"))]
12pub mod addr2line;
13
14#[derive(clap::Parser)]
15pub struct GeneralOpts {
16    /// Use verbose output (-v info, -vv debug, -vvv trace).
17    #[clap(long = "verbose", short = 'v', action = clap::ArgAction::Count)]
18    verbose: u8,
19
20    /// Configuration over whether terminal colors are used in output.
21    ///
22    /// Supports one of `auto|never|always|always-ansi`. The default is to
23    /// detect what to do based on the terminal environment, for example by
24    /// using `isatty`.
25    #[clap(long = "color", default_value = "auto")]
26    pub color: ColorChoice,
27}
28
29impl GeneralOpts {
30    /// Initializes the logger based on the verbosity level.
31    pub fn init_logger(&self) {
32        let default = match self.verbose {
33            0 => "warn",
34            1 => "info",
35            2 => "debug",
36            _ => "trace",
37        };
38
39        env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default))
40            .format_target(false)
41            .init();
42    }
43}
44
45// This is intended to be included in a struct as:
46//
47//      #[clap(flatten)]
48//      io: wasm_tools::InputOutput,
49//
50// and then the methods are used to read the arguments,
51#[derive(clap::Parser)]
52pub struct InputOutput {
53    #[clap(flatten)]
54    input: InputArg,
55
56    #[clap(flatten)]
57    output: OutputArg,
58
59    #[clap(flatten)]
60    general: GeneralOpts,
61}
62
63#[derive(clap::Parser)]
64pub struct InputArg {
65    /// Input file to process.
66    ///
67    /// If not provided or if this is `-` then stdin is read entirely and
68    /// processed. Note that for most subcommands this input can either be a
69    /// binary `*.wasm` file or a textual format `*.wat` file.
70    input: Option<PathBuf>,
71
72    /// Optionally generate DWARF debugging information from WebAssembly text
73    /// files.
74    ///
75    /// When the input to this command is a WebAssembly text file, such as
76    /// `*.wat`, then this option will instruct the text parser to insert DWARF
77    /// debugging information to map binary locations back to the original
78    /// source locations in the input `*.wat` file. This option has no effect if
79    /// the `INPUT` argument is already a WebAssembly binary or if the text
80    /// format uses `(module binary ...)`.
81    #[clap(
82        long,
83        value_name = "lines|full",
84        conflicts_with = "generate_full_dwarf"
85    )]
86    generate_dwarf: Option<GenerateDwarf>,
87
88    /// Shorthand for `--generate-dwarf full`
89    #[clap(short, conflicts_with = "generate_dwarf")]
90    generate_full_dwarf: bool,
91}
92
93#[derive(Copy, Clone)]
94enum GenerateDwarf {
95    Lines,
96    Full,
97}
98
99impl FromStr for GenerateDwarf {
100    type Err = anyhow::Error;
101
102    fn from_str(s: &str) -> Result<GenerateDwarf> {
103        match s {
104            "lines" => Ok(GenerateDwarf::Lines),
105            "full" => Ok(GenerateDwarf::Full),
106            other => bail!("unknown `--generate-dwarf` setting: {other}"),
107        }
108    }
109}
110
111impl InputArg {
112    pub fn parse_wasm(&self) -> Result<Vec<u8>> {
113        let mut parser = wat::Parser::new();
114        match (self.generate_full_dwarf, self.generate_dwarf) {
115            (false, Some(GenerateDwarf::Lines)) => {
116                parser.generate_dwarf(wat::GenerateDwarf::Lines);
117            }
118            (true, _) | (false, Some(GenerateDwarf::Full)) => {
119                parser.generate_dwarf(wat::GenerateDwarf::Full);
120            }
121            (false, None) => {}
122        }
123        if let Some(path) = &self.input {
124            if path != Path::new("-") {
125                let bytes = parser.parse_file(path)?;
126                return Ok(bytes);
127            }
128        }
129        let mut stdin = Vec::new();
130        std::io::stdin()
131            .read_to_end(&mut stdin)
132            .context("failed to read <stdin>")?;
133        let bytes = parser.parse_bytes(Some("<stdin>".as_ref()), &stdin)?;
134        Ok(bytes.into_owned())
135    }
136}
137
138#[derive(clap::Parser)]
139pub struct OutputArg {
140    /// Where to place output.
141    ///
142    /// If not provided then stdout is used.
143    #[clap(short, long)]
144    output: Option<PathBuf>,
145}
146
147pub enum Output<'a> {
148    #[cfg(feature = "component")]
149    Wit {
150        wit: &'a wit_component::DecodedWasm,
151        printer: wit_component::WitPrinter,
152    },
153    Wasm(&'a [u8]),
154    Wat {
155        wasm: &'a [u8],
156        config: wasmprinter::Config,
157    },
158    Json(&'a str),
159}
160
161impl InputOutput {
162    pub fn parse_input_wasm(&self) -> Result<Vec<u8>> {
163        self.input.parse_wasm()
164    }
165
166    pub fn output_wasm(&self, wasm: &[u8], wat: bool) -> Result<()> {
167        if wat {
168            self.output(Output::Wat {
169                wasm,
170                config: Default::default(),
171            })
172        } else {
173            self.output(Output::Wasm(wasm))
174        }
175    }
176
177    pub fn output(&self, bytes: Output<'_>) -> Result<()> {
178        self.output.output(&self.general, bytes)
179    }
180
181    pub fn output_writer(&self) -> Result<Box<dyn WriteColor>> {
182        self.output.output_writer(self.general.color)
183    }
184
185    pub fn output_path(&self) -> Option<&Path> {
186        self.output.output.as_deref()
187    }
188
189    pub fn input_path(&self) -> Option<&Path> {
190        self.input.input.as_deref()
191    }
192
193    pub fn general_opts(&self) -> &GeneralOpts {
194        &self.general
195    }
196}
197
198impl OutputArg {
199    pub fn output_wasm(&self, general: &GeneralOpts, wasm: &[u8], wat: bool) -> Result<()> {
200        if wat {
201            self.output(
202                general,
203                Output::Wat {
204                    wasm,
205                    config: Default::default(),
206                },
207            )
208        } else {
209            self.output(general, Output::Wasm(wasm))
210        }
211    }
212
213    pub fn output(&self, general: &GeneralOpts, output: Output<'_>) -> Result<()> {
214        match output {
215            Output::Wat { wasm, config } => {
216                let mut writer = self.output_writer(general.color)?;
217                config.print(wasm, &mut wasmprinter::PrintTermcolor(&mut writer))
218            }
219            Output::Wasm(bytes) => {
220                match &self.output {
221                    Some(path) => {
222                        std::fs::write(path, bytes)
223                            .context(format!("failed to write `{}`", path.display()))?;
224                    }
225                    None => {
226                        let mut stdout = std::io::stdout();
227                        if stdout.is_terminal() {
228                            bail!("cannot print binary wasm output to a terminal, pass the `-t` flag to print the text format");
229                        }
230                        stdout
231                            .write_all(bytes)
232                            .context("failed to write to stdout")?;
233                    }
234                }
235                Ok(())
236            }
237            Output::Json(s) => self.output_str(s),
238            #[cfg(feature = "component")]
239            Output::Wit { wit, mut printer } => {
240                let resolve = wit.resolve();
241                let ids = resolve
242                    .packages
243                    .iter()
244                    .map(|(id, _)| id)
245                    .filter(|id| *id != wit.package())
246                    .collect::<Vec<_>>();
247                printer.print(resolve, wit.package(), &ids)?;
248                let output = printer.output.to_string();
249                self.output_str(&output)
250            }
251        }
252    }
253
254    fn output_str(&self, output: &str) -> Result<()> {
255        match &self.output {
256            Some(path) => {
257                std::fs::write(path, output)
258                    .context(format!("failed to write `{}`", path.display()))?;
259            }
260            None => std::io::stdout()
261                .write_all(output.as_bytes())
262                .context("failed to write to stdout")?,
263        }
264        Ok(())
265    }
266
267    pub fn output_path(&self) -> Option<&Path> {
268        self.output.as_deref()
269    }
270
271    pub fn output_writer(&self, color: ColorChoice) -> Result<Box<dyn WriteColor>> {
272        match &self.output {
273            Some(output) => {
274                let writer = BufWriter::new(File::create(&output)?);
275                if color == ColorChoice::AlwaysAnsi {
276                    Ok(Box::new(Ansi::new(writer)))
277                } else {
278                    Ok(Box::new(NoColor::new(writer)))
279                }
280            }
281            None => {
282                let stdout = std::io::stdout();
283                if color == ColorChoice::Auto && !stdout.is_terminal() {
284                    Ok(Box::new(StandardStream::stdout(ColorChoice::Never)))
285                } else {
286                    Ok(Box::new(StandardStream::stdout(color)))
287                }
288            }
289        }
290    }
291}