wasmer_cli/commands/package/
build.rs

1use std::path::PathBuf;
2
3use anyhow::Context;
4use dialoguer::console::{Emoji, style};
5use indicatif::ProgressBar;
6use sha2::Digest;
7use wasmer_config::package::PackageHash;
8use wasmer_package::package::Package;
9
10use crate::utils::load_package_manifest;
11
12/// Build a container from a package manifest.
13#[derive(clap::Parser, Debug)]
14pub struct PackageBuild {
15    /// Output path for the package file.
16    /// Defaults to current directory + [name]-[version].webc.
17    #[clap(short = 'o', long)]
18    out: Option<PathBuf>,
19
20    /// Run the publish command without any output
21    #[clap(long)]
22    pub quiet: bool,
23
24    /// Path of the package or wasmer.toml manifest.
25    ///
26    /// Defaults to current directory.
27    package: Option<PathBuf>,
28
29    /// Only checks whether the package could be built successfully
30    #[clap(long)]
31    check: bool,
32}
33
34static READING_MANIFEST_EMOJI: Emoji<'_, '_> = Emoji("📖 ", "");
35static CREATING_OUTPUT_DIRECTORY_EMOJI: Emoji<'_, '_> = Emoji("📁 ", "");
36static WRITING_PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("📦 ", "");
37static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)");
38
39impl PackageBuild {
40    pub(crate) fn check(package_path: PathBuf) -> Self {
41        PackageBuild {
42            out: None,
43            quiet: true,
44            package: Some(package_path),
45            check: true,
46        }
47    }
48
49    pub(crate) fn execute(&self) -> Result<(Package, PackageHash), anyhow::Error> {
50        let manifest_path = self.manifest_path()?;
51        let Some((_, manifest)) = load_package_manifest(&manifest_path)? else {
52            anyhow::bail!(
53                "Could not locate manifest in path '{}'",
54                manifest_path.display()
55            )
56        };
57        let pkg = Package::from_manifest(manifest_path.clone()).context(format!(
58            "While parsing the manifest (loaded from {})",
59            manifest_path.canonicalize()?.display()
60        ))?;
61        let data = pkg.serialize().context("While validating the package")?;
62        let hash = sha2::Sha256::digest(&data).into();
63        let pkg_hash = PackageHash::from_sha256_bytes(hash);
64
65        let name = if let Some(manifest_pkg) = manifest.package {
66            if let Some(name) = manifest_pkg.name {
67                if let Some(version) = manifest_pkg.version {
68                    format!("{}-{}.webc", name.replace('/', "-"), version)
69                } else {
70                    format!("{}-{}.webc", name.replace('/', "-"), pkg_hash)
71                }
72            } else {
73                format!("{pkg_hash}.webc")
74            }
75        } else {
76            format!("{pkg_hash}.webc")
77        };
78
79        // Setup the progress bar
80        let pb = if self.quiet {
81            ProgressBar::hidden()
82        } else {
83            ProgressBar::new_spinner()
84        };
85
86        pb.println(format!(
87            "{} {}Reading manifest...",
88            style("[1/3]").bold().dim(),
89            READING_MANIFEST_EMOJI
90        ));
91
92        // rest of the code writes the package to disk and is irrelevant
93        // to checking.
94        if self.check {
95            return Ok((pkg, pkg_hash));
96        }
97
98        pb.println(format!(
99            "{} {}Creating output directory...",
100            style("[2/3]").bold().dim(),
101            CREATING_OUTPUT_DIRECTORY_EMOJI
102        ));
103
104        let out_path = if let Some(p) = &self.out {
105            if p.is_dir() {
106                p.join(name)
107            } else {
108                if let Some(parent) = p.parent() {
109                    std::fs::create_dir_all(parent).context("could not create output directory")?;
110                }
111
112                p.to_owned()
113            }
114        } else {
115            std::env::current_dir()
116                .context("could not determine current directory")?
117                .join(name)
118        };
119
120        if out_path.exists() {
121            anyhow::bail!(
122                "Output path '{}' already exists - specify a different path with -o/--out",
123                out_path.display()
124            );
125        }
126
127        pb.println(format!(
128            "{} {}Writing package...",
129            style("[3/3]").bold().dim(),
130            WRITING_PACKAGE_EMOJI
131        ));
132
133        std::fs::write(&out_path, &data)
134            .with_context(|| format!("could not write contents to '{}'", out_path.display()))?;
135
136        pb.finish_with_message(format!(
137            "{} Package written to '{}'",
138            SPARKLE,
139            out_path.display()
140        ));
141
142        Ok((pkg, pkg_hash))
143    }
144
145    fn manifest_path(&self) -> Result<PathBuf, anyhow::Error> {
146        let path = if let Some(p) = &self.package {
147            if p.is_dir() {
148                let manifest_path = p.join("wasmer.toml");
149                if !manifest_path.is_file() {
150                    anyhow::bail!(
151                        "Specified directory '{}' does not contain a wasmer.toml manifest",
152                        p.display()
153                    );
154                }
155                manifest_path
156            } else if p.is_file() {
157                p.clone()
158            } else {
159                anyhow::bail!(
160                    "Specified path '{}' is not a file or directory",
161                    p.display()
162                );
163            }
164        } else {
165            let dir = std::env::current_dir().context("could not get current directory")?;
166            let manifest_path = dir.join("wasmer.toml");
167            if !manifest_path.is_file() {
168                anyhow::bail!(
169                    "Current directory '{}' does not contain a wasmer.toml manifest - specify a path with --package-dir",
170                    dir.display()
171                );
172            }
173            manifest_path
174        };
175
176        Ok(path)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use wasmer_package::utils::from_disk;
183
184    use super::*;
185
186    /// Download a package from the dev registry.
187    #[test]
188    fn test_cmd_package_build() {
189        let dir = tempfile::tempdir().unwrap();
190        let path = dir.path();
191
192        std::fs::write(
193            path.join("wasmer.toml"),
194            r#"
195[package]
196name = "wasmer/hello"
197version = "0.1.0"
198description = "hello"
199
200[fs]
201"data" = "data"
202"#,
203        )
204        .unwrap();
205
206        std::fs::create_dir(path.join("data")).unwrap();
207        std::fs::write(path.join("data").join("hello.txt"), "Hello, world!").unwrap();
208
209        let cmd = PackageBuild {
210            package: Some(path.to_owned()),
211            out: Some(path.to_owned()),
212            quiet: true,
213            check: false,
214        };
215
216        cmd.execute().unwrap();
217
218        from_disk(path.join("wasmer-hello-0.1.0.webc")).unwrap();
219    }
220}