1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! WASM Builder

use crate::utils::Result;
use anyhow::anyhow;
use cargo_metadata::{Metadata, MetadataCommand, Package};
use etc::{Etc, FileSystem};
use std::{fs, path::PathBuf, process::Command};

/// WASM Builder
pub struct WasmBuilder {
    metadata: Metadata,
    package: Package,
    output: Option<PathBuf>,
    out_dir: Option<PathBuf>,
}

impl WasmBuilder {
    /// Create a new WASM Builder.
    pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
        let mut metadata_command = MetadataCommand::new();
        let path = path.into();

        tracing::trace!("parsing cargo metadata from: {path:?}");
        let metadata = if path.is_dir() {
            metadata_command.current_dir(&path)
        } else {
            metadata_command.manifest_path(&path)
        }
        .exec()?;

        let manifest = Etc::from(path).find("Cargo.toml")?;
        tracing::trace!("expected manifest: {manifest:?}");
        let package = metadata
            .packages
            .iter()
            .find(|p| p.manifest_path.ends_with(&manifest))
            .ok_or(anyhow!("package {manifest:?} not found"))?
            .clone();

        Ok(Self {
            metadata,
            package,
            output: None,
            out_dir: None,
        })
    }

    /// Get the output filename.
    pub fn output(&self) -> Result<PathBuf> {
        let out_dir = self.out_dir()?;
        let output = if let Some(output) = self.output.as_ref() {
            output.into()
        } else {
            out_dir
                .join(self.package.name.as_str())
                .with_extension("wasm")
        };

        Ok(output)
    }

    /// Set the output filename.
    pub fn with_output(&mut self, output: impl Into<PathBuf>) -> &mut Self {
        self.output = Some(output.into());
        self
    }

    /// Get the output directory.
    pub fn out_dir(&self) -> Result<PathBuf> {
        let out_dir: PathBuf = if let Some(out_dir) = self.out_dir.as_ref() {
            out_dir.into()
        } else {
            let out_dir = self.metadata.target_directory.join("zink");
            if !out_dir.exists() {
                fs::create_dir_all(&out_dir)?;
            }
            out_dir.into()
        };

        Ok(out_dir)
    }

    /// Set the output directory.
    pub fn with_out_dir(&mut self, out_dir: impl Into<PathBuf>) -> &mut Self {
        self.out_dir = Some(out_dir.into());
        self
    }

    /// Run the WASM Builder.
    pub fn build(&self) -> Result<()> {
        self.compile()?;
        self.post()?;
        Ok(())
    }

    /// Compile project to WASM.
    fn compile(&self) -> Result<()> {
        let args = vec![
            "build",
            "--manifest-path",
            self.package.manifest_path.as_str(),
            "--target",
            "wasm32-unknown-unknown",
            "--release",
        ];

        Command::new("cargo").args(&args).status()?;
        Ok(())
    }

    /// Post processing the built WASM files.
    fn post(&self) -> Result<()> {
        let src = self
            .metadata
            .target_directory
            .join("wasm32-unknown-unknown")
            .join("release")
            .join(self.package.name.as_str())
            .with_extension("wasm");

        // run the wasm optimizer
        zinkc::utils::wasm_opt(src, self.output()?)?;

        Ok(())
    }
}