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