wasmer_cli/commands/package/
build.rs1use 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#[derive(clap::Parser, Debug)]
14pub struct PackageBuild {
15 #[clap(short = 'o', long)]
18 out: Option<PathBuf>,
19
20 #[clap(long)]
22 pub quiet: bool,
23
24 package: Option<PathBuf>,
28
29 #[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 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 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 #[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}