xtask_wasm/
wasm_opt.rs

1use crate::anyhow::{anyhow, ensure, Context, Result};
2use lazy_static::lazy_static;
3use std::{
4    fs,
5    path::{Path, PathBuf},
6    process,
7};
8
9lazy_static! {
10    static ref WASM_OPT_URL: String = {
11        // Any update on the version needs to be done in the README too.
12        let version = "123";
13
14        let os = std::env::consts::OS;
15        let mut arch = std::env::consts::ARCH;
16        if arch == "aarch64" {
17            arch = "arm64";
18        }
19
20        format!(
21                "https://github.com/WebAssembly/binaryen/releases/download/version_{version}/binaryen-version_{version}-{arch}-{os}.tar.gz",
22            )
23    };
24}
25
26#[allow(clippy::needless_question_mark)]
27fn download_wasm_opt() -> Result<&'static Path> {
28    lazy_static! {
29        static ref WASM_OPT_PATH: Result<PathBuf> = {
30            fn downloaded_binary_path() -> Result<PathBuf> {
31                let cache =
32                    binary_install::Cache::at(crate::metadata().target_directory.as_std_path());
33
34                #[cfg(target_os = "macos")]
35                let binaries = &["wasm-opt", "libbinaryen"];
36                #[cfg(not(target_os = "macos"))]
37                let binaries = &["wasm-opt"];
38
39                log::info!("Downloading wasm-opt");
40                Ok(cache
41                    .download(true, "wasm-opt", binaries, &WASM_OPT_URL)
42                    .with_context(|| {
43                        format!("could not download wasm-opt: {}", &WASM_OPT_URL.as_str())
44                    })?
45                    .expect("install_permitted is always true; qed")
46                    .binary("wasm-opt")?)
47            }
48
49            downloaded_binary_path()
50        };
51    }
52
53    WASM_OPT_PATH.as_deref().map_err(|err| anyhow!("{}", err))
54}
55
56/// Helper Abstracting the `wasm-opt` binary from
57/// [binaryen](https://github.com/WebAssembly/binaryen) for easily optimizing
58/// your Wasm binary.
59///
60/// # Usage
61///
62/// ```rust,no_run
63/// # use xtask_wasm::{anyhow::Result, WasmOpt};
64/// # fn main() -> Result<()> {
65/// WasmOpt::level(1)
66///     .shrink(2)
67///     .optimize("app.wasm")?;
68/// # Ok(())
69/// # }
70/// ```
71pub struct WasmOpt {
72    /// How much to focus on optimizing code.
73    pub optimization_level: u32,
74    /// How much to focus on shrinking code size.
75    pub shrink_level: u32,
76    /// Emit names section in Wasm binary.
77    pub debug_info: bool,
78}
79
80impl WasmOpt {
81    /// Set the level of code optimization.
82    pub fn level(optimization_level: u32) -> Self {
83        Self {
84            optimization_level,
85            shrink_level: 0,
86            debug_info: false,
87        }
88    }
89
90    /// Set the level of size shrinking.
91    pub fn shrink(mut self, shrink_level: u32) -> Self {
92        self.shrink_level = shrink_level;
93        self
94    }
95
96    /// Preserve debug info.
97    pub fn debug(mut self) -> Self {
98        self.debug_info = true;
99        self
100    }
101
102    /// Optimize the Wasm binary provided by `binary_path`.
103    ///
104    /// This function will execute `wasm-opt` over the given Wasm binary,
105    /// downloading it if necessary (cached into the `target` directory).
106    pub fn optimize(self, binary_path: impl AsRef<Path>) -> Result<Self> {
107        let input_path = binary_path.as_ref();
108        let output_path = input_path.with_extension("opt");
109        let wasm_opt = download_wasm_opt()?;
110
111        let mut command = process::Command::new(wasm_opt);
112        command
113            .stderr(process::Stdio::inherit())
114            .arg(input_path)
115            .arg("-o")
116            .arg(&output_path)
117            .arg("-O")
118            .arg("-ol")
119            .arg(self.optimization_level.to_string())
120            .arg("-s")
121            .arg(self.shrink_level.to_string());
122
123        if self.debug_info {
124            command.arg("-g");
125        }
126
127        #[cfg(target_os = "macos")]
128        {
129            command.env("DYLD_LIBRARY_PATH", wasm_opt.parent().unwrap());
130        }
131
132        log::info!("Optimizing Wasm");
133        ensure!(
134            command.output()?.status.success(),
135            "command `wasm-opt` failed"
136        );
137
138        fs::remove_file(input_path)?;
139        fs::rename(&output_path, input_path)?;
140
141        log::info!("Wasm optimized");
142        Ok(self)
143    }
144}