wasmer_wasix/bin_factory/
binary_package.rs1use std::{path::Path, sync::Arc};
2
3use anyhow::Context;
4use once_cell::sync::OnceCell;
5use sha2::Digest;
6use virtual_fs::UnionFileSystem;
7use wasmer_config::package::{
8 PackageHash, PackageId, PackageSource, SuggestedCompilerOptimizations,
9};
10use wasmer_package::package::Package;
11use webc::Container;
12use webc::compat::SharedBytes;
13
14use crate::{
15 Runtime,
16 runners::MappedDirectory,
17 runtime::resolver::{PackageInfo, ResolveError},
18};
19use wasmer_types::ModuleHash;
20
21#[derive(derive_more::Debug, Clone)]
22pub struct BinaryPackageCommand {
23 name: String,
24 metadata: webc::metadata::Command,
25 #[debug(ignore)]
26 pub(crate) atom: SharedBytes,
27 hash: ModuleHash,
28 features: Option<wasmer_types::Features>,
29 pub suggested_compiler_optimizations: SuggestedCompilerOptimizations,
30}
31
32impl BinaryPackageCommand {
33 pub fn new(
34 name: String,
35 metadata: webc::metadata::Command,
36 atom: SharedBytes,
37 hash: ModuleHash,
38 features: Option<wasmer_types::Features>,
39 suggested_compiler_optimizations: SuggestedCompilerOptimizations,
40 ) -> Self {
41 Self {
42 name,
43 metadata,
44 atom,
45 hash,
46 features,
47 suggested_compiler_optimizations,
48 }
49 }
50
51 pub fn name(&self) -> &str {
52 &self.name
53 }
54
55 pub fn metadata(&self) -> &webc::metadata::Command {
56 &self.metadata
57 }
58
59 pub fn atom(&self) -> SharedBytes {
62 self.atom.clone()
63 }
64
65 pub fn atom_ref(&self) -> &SharedBytes {
68 &self.atom
69 }
70
71 pub fn hash(&self) -> &ModuleHash {
72 &self.hash
73 }
74
75 pub fn wasm_features(&self) -> Option<wasmer_types::Features> {
77 if let Some(features) = &self.features {
79 return Some(features.clone());
80 }
81
82 None
84 }
85}
86
87#[derive(Debug, Clone)]
89pub struct BinaryPackage {
90 pub id: PackageId,
91 pub package_ids: Vec<PackageId>,
93
94 pub when_cached: Option<u128>,
95 pub entrypoint_cmd: Option<String>,
98 pub hash: OnceCell<ModuleHash>,
99 pub webc_fs: Option<Arc<UnionFileSystem>>,
103 pub commands: Vec<BinaryPackageCommand>,
104 pub uses: Vec<String>,
105 pub file_system_memory_footprint: u64,
106
107 pub additional_host_mapped_directories: Vec<MappedDirectory>,
108}
109
110impl BinaryPackage {
111 #[tracing::instrument(level = "debug", skip_all)]
112 pub async fn from_dir(
113 dir: &Path,
114 rt: &(dyn Runtime + Send + Sync),
115 ) -> Result<Self, anyhow::Error> {
116 let source = rt.source();
117
118 let hash = sha2::Sha256::digest(dir.display().to_string().as_bytes()).into();
121 let id = PackageId::Hash(PackageHash::from_sha256_bytes(hash));
122
123 let manifest_path = dir.join("wasmer.toml");
124 let webc = Package::from_manifest(&manifest_path)?;
125 let container = Container::from(webc);
126 let manifest = container.manifest();
127
128 let root = PackageInfo::from_manifest(id, manifest, container.version())?;
129 let root_id = root.id.clone();
130
131 let resolution = crate::runtime::resolver::resolve(&root_id, &root, &*source).await?;
132 let mut pkg = rt
133 .package_loader()
134 .load_package_tree(&container, &resolution, true)
135 .await
136 .map_err(|e| anyhow::anyhow!(e))?;
137
138 let wasmer_toml = std::fs::read_to_string(&manifest_path).unwrap();
141 let wasmer_toml: wasmer_config::package::Manifest = toml::from_str(&wasmer_toml).unwrap();
142 pkg.additional_host_mapped_directories.extend(
143 wasmer_toml
144 .fs
145 .into_iter()
146 .map(|(guest, host)| {
147 anyhow::Ok(MappedDirectory {
148 host: dir.join(host).canonicalize()?,
149 guest,
150 })
151 })
152 .collect::<Result<Vec<_>, _>>()?
153 .into_iter(),
154 );
155
156 Ok(pkg)
157 }
158
159 #[tracing::instrument(level = "debug", skip_all)]
162 pub async fn from_webc(
163 container: &Container,
164 rt: &(dyn Runtime + Send + Sync),
165 ) -> Result<Self, anyhow::Error> {
166 let source = rt.source();
167
168 let manifest = container.manifest();
169 let id = PackageInfo::package_id_from_manifest(manifest)?
170 .or_else(|| {
171 container
172 .webc_hash()
173 .map(|hash| PackageId::Hash(PackageHash::from_sha256_bytes(hash)))
174 })
175 .ok_or_else(|| anyhow::Error::msg("webc file did not provide its hash"))?;
176
177 let root = PackageInfo::from_manifest(id, manifest, container.version())?;
178 let root_id = root.id.clone();
179
180 let resolution = crate::runtime::resolver::resolve(&root_id, &root, &*source).await?;
181 let pkg = rt
182 .package_loader()
183 .load_package_tree(container, &resolution, false)
184 .await
185 .map_err(|e| anyhow::anyhow!(e))?;
186
187 Ok(pkg)
188 }
189
190 #[tracing::instrument(level = "debug", skip_all)]
192 pub async fn from_registry(
193 specifier: &PackageSource,
194 runtime: &(dyn Runtime + Send + Sync),
195 ) -> Result<Self, anyhow::Error> {
196 let source = runtime.source();
197 let root_summary =
198 source
199 .latest(specifier)
200 .await
201 .map_err(|error| ResolveError::Registry {
202 package: specifier.clone(),
203 error,
204 })?;
205 let root = runtime.package_loader().load(&root_summary).await?;
206 let id = root_summary.package_id();
207
208 let resolution = crate::runtime::resolver::resolve(&id, &root_summary.pkg, &source)
209 .await
210 .context("Dependency resolution failed")?;
211 let pkg = runtime
212 .package_loader()
213 .load_package_tree(&root, &resolution, false)
214 .await
215 .map_err(|e| anyhow::anyhow!(e))?;
216
217 Ok(pkg)
218 }
219
220 pub fn get_command(&self, name: &str) -> Option<&BinaryPackageCommand> {
221 self.commands.iter().find(|cmd| cmd.name() == name)
222 }
223
224 pub fn get_entrypoint_command(&self) -> Option<&BinaryPackageCommand> {
226 self.entrypoint_cmd
227 .as_deref()
228 .and_then(|name| self.get_command(name))
229 }
230
231 #[deprecated(
233 note = "Use BinaryPackage::get_entrypoint_command instead",
234 since = "0.22.0"
235 )]
236 pub fn entrypoint_bytes(&self) -> Option<SharedBytes> {
237 self.get_entrypoint_command().map(|entry| entry.atom())
238 }
239
240 pub fn hash(&self) -> ModuleHash {
244 *self.hash.get_or_init(|| {
245 if let Some(cmd) = self.get_entrypoint_command() {
246 cmd.hash
247 } else {
248 ModuleHash::new(self.id.to_string())
249 }
250 })
251 }
252
253 pub fn infer_entrypoint(&self) -> Result<&str, anyhow::Error> {
254 if let Some(entrypoint) = self.entrypoint_cmd.as_deref() {
255 return Ok(entrypoint);
256 }
257
258 match self.commands.as_slice() {
259 [] => anyhow::bail!("The package doesn't contain any executable commands"),
260 [one] => Ok(one.name()),
261 [..] => {
262 let mut commands: Vec<_> = self.commands.iter().map(|cmd| cmd.name()).collect();
263 commands.sort();
264 anyhow::bail!(
265 "Unable to determine the package's entrypoint. Please choose one of {commands:?}"
266 );
267 }
268 }
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use sha2::Digest;
275 use tempfile::TempDir;
276 use virtual_fs::{AsyncReadExt, FileSystem as _};
277 use wasmer_package::utils::from_disk;
278
279 use crate::{
280 PluggableRuntime,
281 runtime::{package_loader::BuiltinPackageLoader, task_manager::VirtualTaskManager},
282 };
283
284 use super::*;
285
286 fn task_manager() -> Arc<dyn VirtualTaskManager + Send + Sync> {
287 cfg_if::cfg_if! {
288 if #[cfg(feature = "sys-thread")] {
289 Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::new(tokio::runtime::Handle::current()))
290 } else {
291 unimplemented!("Unable to get the task manager")
292 }
293 }
294 }
295
296 #[tokio::test]
297 #[cfg_attr(
298 not(feature = "sys-thread"),
299 ignore = "The tokio task manager isn't available on this platform"
300 )]
301 async fn fs_table_can_map_directories_to_different_names() {
302 let temp = TempDir::new().unwrap();
303 let wasmer_toml = r#"
304 [package]
305 name = "some/package"
306 version = "0.0.0"
307 description = "a dummy package"
308
309 [fs]
310 "/public" = "./out"
311 "#;
312 let manifest = temp.path().join("wasmer.toml");
313 std::fs::write(&manifest, wasmer_toml).unwrap();
314 let out = temp.path().join("out");
315 std::fs::create_dir_all(&out).unwrap();
316 let file_txt = "Hello, World!";
317 std::fs::write(out.join("file.txt"), file_txt).unwrap();
318 let tasks = task_manager();
319 let mut runtime = PluggableRuntime::new(tasks);
320 runtime.set_package_loader(
321 BuiltinPackageLoader::new()
322 .with_shared_http_client(runtime.http_client().unwrap().clone()),
323 );
324
325 let pkg = Package::from_manifest(&manifest).unwrap();
326 let data = pkg.serialize().unwrap();
327 let webc_path = temp.path().join("package.webc");
328 std::fs::write(&webc_path, data).unwrap();
329
330 let pkg = BinaryPackage::from_webc(&from_disk(&webc_path).unwrap(), &runtime)
331 .await
332 .unwrap();
333
334 let mut f = pkg
337 .webc_fs
338 .as_ref()
339 .expect("no webc fs")
340 .new_open_options()
341 .read(true)
342 .open("/public/file.txt")
343 .unwrap();
344 let mut buffer = String::new();
345 f.read_to_string(&mut buffer).await.unwrap();
346 assert_eq!(buffer, file_txt);
347 }
348
349 #[tokio::test]
350 #[cfg_attr(
351 not(feature = "sys-thread"),
352 ignore = "The tokio task manager isn't available on this platform"
353 )]
354 async fn commands_use_the_atom_signature() {
355 let temp = TempDir::new().unwrap();
356 let wasmer_toml = r#"
357 [package]
358 name = "some/package"
359 version = "0.0.0"
360 description = "a dummy package"
361
362 [[module]]
363 name = "foo"
364 source = "foo.wasm"
365 abi = "wasi"
366
367 [[command]]
368 name = "cmd"
369 module = "foo"
370 "#;
371 let manifest = temp.path().join("wasmer.toml");
372 std::fs::write(&manifest, wasmer_toml).unwrap();
373
374 let atom_path = temp.path().join("foo.wasm");
375 std::fs::write(&atom_path, b"").unwrap();
376
377 let webc: Container = Package::from_manifest(&manifest).unwrap().into();
378
379 let tasks = task_manager();
380 let mut runtime = PluggableRuntime::new(tasks);
381 runtime.set_package_loader(
382 BuiltinPackageLoader::new()
383 .with_shared_http_client(runtime.http_client().unwrap().clone()),
384 );
385
386 let pkg = BinaryPackage::from_dir(temp.path(), &runtime)
387 .await
388 .unwrap();
389
390 assert_eq!(pkg.commands.len(), 1);
391 let command = pkg.get_command("cmd").unwrap();
392 let atom_sha256_hash = sha2::Sha256::digest(webc.get_atom("foo").unwrap()).into();
393 let module_hash = ModuleHash::from_bytes(atom_sha256_hash);
394 assert_eq!(command.hash(), &module_hash);
395 }
396}