elko/utils/
wasm.rs

1//! WASM Builder
2
3use crate::utils::Result;
4use anyhow::anyhow;
5use cargo_metadata::{Metadata, MetadataCommand, Package};
6use etc::{Etc, FileSystem};
7use std::{fs, path::PathBuf, process::Command};
8
9/// WASM Builder
10pub struct WasmBuilder {
11    metadata: Metadata,
12    package: Package,
13    output: Option<PathBuf>,
14    out_dir: Option<PathBuf>,
15}
16
17impl WasmBuilder {
18    /// Create a new WASM Builder.
19    pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
20        let mut metadata_command = MetadataCommand::new();
21        let path = path.into();
22
23        tracing::trace!("parsing cargo metadata from: {path:?}");
24        let metadata = if path.is_dir() {
25            metadata_command.current_dir(&path)
26        } else {
27            metadata_command.manifest_path(&path)
28        }
29        .exec()?;
30
31        let manifest = Etc::from(path).find("Cargo.toml")?;
32        tracing::trace!("expected manifest: {manifest:?}");
33        let package = metadata
34            .packages
35            .iter()
36            .find(|p| p.manifest_path.ends_with(&manifest))
37            .ok_or(anyhow!("package {manifest:?} not found"))?
38            .clone();
39
40        Ok(Self {
41            metadata,
42            package,
43            output: None,
44            out_dir: None,
45        })
46    }
47
48    /// Get the output filename.
49    pub fn output(&self) -> Result<PathBuf> {
50        let out_dir = self.out_dir()?;
51        let output = if let Some(output) = self.output.as_ref() {
52            output.into()
53        } else {
54            out_dir
55                .join(self.package.name.as_str())
56                .with_extension("wasm")
57        };
58
59        Ok(output)
60    }
61
62    /// Set the output filename.
63    pub fn with_output(&mut self, output: impl Into<PathBuf>) -> &mut Self {
64        self.output = Some(output.into());
65        self
66    }
67
68    /// Get the output directory.
69    pub fn out_dir(&self) -> Result<PathBuf> {
70        let out_dir: PathBuf = if let Some(out_dir) = self.out_dir.as_ref() {
71            out_dir.into()
72        } else {
73            let out_dir = self.metadata.target_directory.join("zink");
74            if !out_dir.exists() {
75                fs::create_dir_all(&out_dir)?;
76            }
77            out_dir.into()
78        };
79
80        Ok(out_dir)
81    }
82
83    /// Set the output directory.
84    pub fn with_out_dir(&mut self, out_dir: impl Into<PathBuf>) -> &mut Self {
85        self.out_dir = Some(out_dir.into());
86        self
87    }
88
89    /// Run the WASM Builder.
90    pub fn build(&self) -> Result<()> {
91        self.compile()?;
92        self.post()?;
93        Ok(())
94    }
95
96    /// Compile project to WASM.
97    fn compile(&self) -> Result<()> {
98        let args = vec![
99            "build",
100            "--manifest-path",
101            self.package.manifest_path.as_str(),
102            "--target",
103            "wasm32-unknown-unknown",
104            "--release",
105        ];
106
107        Command::new("cargo").args(&args).status()?;
108        Ok(())
109    }
110
111    /// Post processing the built WASM files.
112    fn post(&self) -> Result<()> {
113        let src = self
114            .metadata
115            .target_directory
116            .join("wasm32-unknown-unknown")
117            .join("release")
118            .join(self.package.name.as_str())
119            .with_extension("wasm");
120
121        // run the wasm optimizer
122        zinkc::utils::wasm_opt(src, self.output()?)?;
123
124        Ok(())
125    }
126}