Skip to main content

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/// ```
71#[derive(Debug)]
72pub struct WasmOpt {
73    /// How much to focus on optimizing code.
74    pub optimization_level: u32,
75    /// How much to focus on shrinking code size.
76    pub shrink_level: u32,
77    /// Emit names section in Wasm binary.
78    pub debug_info: bool,
79}
80
81impl WasmOpt {
82    /// Set the level of code optimization.
83    pub fn level(optimization_level: u32) -> Self {
84        Self {
85            optimization_level,
86            shrink_level: 0,
87            debug_info: false,
88        }
89    }
90
91    /// Set the level of size shrinking.
92    pub fn shrink(mut self, shrink_level: u32) -> Self {
93        self.shrink_level = shrink_level;
94        self
95    }
96
97    /// Preserve debug info.
98    pub fn debug(mut self) -> Self {
99        self.debug_info = true;
100        self
101    }
102
103    /// Optimize the Wasm binary provided by `binary_path`.
104    ///
105    /// This function will execute `wasm-opt` over the given Wasm binary,
106    /// downloading it if necessary (cached into the `target` directory).
107    pub fn optimize(self, binary_path: impl AsRef<Path>) -> Result<Self> {
108        let input_path = binary_path.as_ref();
109        let output_path = input_path.with_extension("opt");
110        let wasm_opt = download_wasm_opt()?;
111
112        let mut command = process::Command::new(wasm_opt);
113        command
114            .stderr(process::Stdio::inherit())
115            .arg(input_path)
116            .arg("-o")
117            .arg(&output_path)
118            .arg("-O")
119            .arg("-ol")
120            .arg(self.optimization_level.to_string())
121            .arg("-s")
122            .arg(self.shrink_level.to_string());
123
124        if self.debug_info {
125            command.arg("-g");
126        }
127
128        #[cfg(target_os = "macos")]
129        {
130            command.env("DYLD_LIBRARY_PATH", wasm_opt.parent().unwrap());
131        }
132
133        log::info!("Optimizing Wasm");
134        ensure!(
135            command.output()?.status.success(),
136            "command `wasm-opt` failed"
137        );
138
139        fs::remove_file(input_path)?;
140        fs::rename(&output_path, input_path)?;
141
142        log::info!("Wasm optimized");
143        Ok(self)
144    }
145}