1#![allow(
2 clippy::result_large_err,
3 reason = "WasmerPackageError is large, but not often used"
4)]
5
6use std::{
7 borrow::Cow,
8 collections::{BTreeMap, BTreeSet},
9 fmt::Debug,
10 fs::File,
11 io::{BufRead, BufReader},
12 path::{Path, PathBuf},
13 sync::Arc,
14};
15
16use anyhow::{Context, Error};
17use bytes::Bytes;
18use flate2::bufread::GzDecoder;
19use shared_buffer::OwnedBuffer;
20use tar::Archive;
21use tempfile::TempDir;
22use wasmer_config::package::Manifest as WasmerManifest;
23
24use webc::{
25 AbstractVolume, AbstractWebc, Container, ContainerError, DetectError, PathSegment, Version,
26 Volume,
27 metadata::{Manifest as WebcManifest, annotations::Wapm},
28 v3::{
29 ChecksumAlgorithm, Timestamps,
30 write::{FileEntry, Writer},
31 },
32};
33
34use super::{
35 ManifestError, MemoryVolume, Strictness,
36 manifest::wasmer_manifest_to_webc,
37 volume::{WasmerPackageVolume, fs::FsVolume},
38};
39
40#[derive(Debug, thiserror::Error)]
42#[allow(clippy::result_large_err)]
43#[non_exhaustive]
44pub enum WasmerPackageError {
45 #[error("Unable to create a temporary directory")]
47 TempDir(#[source] std::io::Error),
48 #[error("Unable to open \"{}\"", path.display())]
50 FileOpen {
51 path: PathBuf,
53 #[source]
55 error: std::io::Error,
56 },
57 #[error("Unable to read \"{}\"", path.display())]
59 FileRead {
60 path: PathBuf,
62 #[source]
64 error: std::io::Error,
65 },
66
67 #[error("IO Error: {0:?}")]
69 IoError(#[from] std::io::Error),
70
71 #[error("Malformed path format: {0:?}")]
73 MalformedPath(PathBuf),
74
75 #[error("Unable to extract the tarball")]
77 Tarball(#[source] std::io::Error),
78 #[error("Unable to deserialize \"{}\"", path.display())]
80 TomlDeserialize {
81 path: PathBuf,
83 #[source]
85 error: toml::de::Error,
86 },
87 #[error("Unable to deserialize \"{}\"", path.display())]
89 JsonDeserialize {
90 path: PathBuf,
92 #[source]
94 error: serde_json::Error,
95 },
96 #[error("Unable to find the \"wasmer.toml\"")]
98 MissingManifest,
99 #[error("Unable to get the absolute path for \"{}\"", path.display())]
101 Canonicalize {
102 path: PathBuf,
104 #[source]
106 error: std::io::Error,
107 },
108 #[error("Unable to load the \"wasmer.toml\" manifest")]
110 Manifest(#[from] ManifestError),
111 #[error("The manifest is invalid")]
113 Validation(#[from] wasmer_config::package::ValidationError),
114 #[error("Path: \"{}\" does not exist", path.display())]
116 PathNotExists {
117 path: PathBuf,
119 },
120 #[error("Volume creation failed: {0:?}")]
122 VolumeCreation(#[from] anyhow::Error),
123
124 #[error("serde error: {0:?}")]
126 SerdeError(#[from] ciborium::value::Error),
127
128 #[error("container error: {0:?}")]
130 ContainerError(#[from] ContainerError),
131
132 #[error("detect error: {0:?}")]
134 DetectError(#[from] DetectError),
135}
136
137#[derive(Clone, Copy)]
141pub struct WalkBuilderFactory {
142 pub walker_factory: fn(&Path) -> ignore::WalkBuilder,
143}
144
145impl Debug for WalkBuilderFactory {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_struct("WalkBuilderFactory").finish()
148 }
149}
150
151impl WalkBuilderFactory {
152 pub fn new(walker_factory: fn(&Path) -> ignore::WalkBuilder) -> Self {
153 Self { walker_factory }
154 }
155
156 pub fn create_walk_builder(&self, path: &Path) -> ignore::WalkBuilder {
157 (self.walker_factory)(path)
158 }
159}
160
161fn is_wasmer_ignore_present(path: &Path) -> bool {
162 path.ancestors().any(|p| p.join(".wasmerignore").is_file())
163}
164
165pub fn wasmer_ignore_walker() -> WalkBuilderFactory {
169 fn walker_factory(root_dir: &Path) -> ignore::WalkBuilder {
170 let mut builder = ignore::WalkBuilder::new(root_dir);
171 builder
173 .standard_filters(false)
174 .follow_links(false)
175 .parents(true)
176 .add_custom_ignore_filename(".wasmerignore");
177
178 if !is_wasmer_ignore_present(root_dir) {
186 let mut overrides = ignore::overrides::OverrideBuilder::new(root_dir);
187 overrides.add("!.git").expect("valid override");
188 builder.overrides(overrides.build().expect("valid overrides"));
189 }
190
191 builder
192 }
193 WalkBuilderFactory::new(walker_factory)
194}
195
196pub fn include_everything_walker() -> WalkBuilderFactory {
197 fn walker_factory(root_dir: &Path) -> ignore::WalkBuilder {
198 let mut builder = ignore::WalkBuilder::new(root_dir);
199 builder.standard_filters(false).follow_links(false);
200 builder
201 }
202 WalkBuilderFactory::new(walker_factory)
203}
204
205#[derive(Debug)]
207pub struct Package {
208 #[allow(dead_code)]
211 base_dir: BaseDir,
212 manifest: WebcManifest,
213 atoms: BTreeMap<String, OwnedBuffer>,
214 strictness: Strictness,
215 volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>>,
216}
217
218impl Package {
219 pub fn from_tarball_file(path: impl AsRef<Path>) -> Result<Self, WasmerPackageError> {
226 Package::from_tarball_file_with_walker(path.as_ref(), include_everything_walker())
227 }
228
229 pub fn from_tarball_file_with_walker(
236 path: impl AsRef<Path>,
237 walker_factory: WalkBuilderFactory,
238 ) -> Result<Self, WasmerPackageError> {
239 Package::from_tarball_file_with_strictness(
240 path.as_ref(),
241 Strictness::default(),
242 walker_factory,
243 )
244 }
245
246 pub fn from_tarball_file_with_strictness(
253 path: impl AsRef<Path>,
254 strictness: Strictness,
255 walker_factory: WalkBuilderFactory,
256 ) -> Result<Self, WasmerPackageError> {
257 let path = path.as_ref();
258 let f = File::open(path).map_err(|error| WasmerPackageError::FileOpen {
259 path: path.to_path_buf(),
260 error,
261 })?;
262
263 Package::from_tarball_with_strictness(BufReader::new(f), strictness, walker_factory)
264 }
265
266 pub fn from_tarball(tarball: impl BufRead) -> Result<Self, WasmerPackageError> {
268 Package::from_tarball_with_walker(tarball, include_everything_walker())
269 }
270
271 pub fn from_tarball_with_walker(
273 tarball: impl BufRead,
274 walker_factory: WalkBuilderFactory,
275 ) -> Result<Self, WasmerPackageError> {
276 Package::from_tarball_with_strictness(tarball, Strictness::default(), walker_factory)
277 }
278
279 pub fn from_tarball_with_strictness(
281 tarball: impl BufRead,
282 strictness: Strictness,
283 walker_factory: WalkBuilderFactory,
284 ) -> Result<Self, WasmerPackageError> {
285 let tarball = GzDecoder::new(tarball);
286 let temp = tempdir().map_err(WasmerPackageError::TempDir)?;
287 let archive = Archive::new(tarball);
288 unpack_archive(archive, temp.path()).map_err(WasmerPackageError::Tarball)?;
289
290 let (_manifest_path, manifest) = read_manifest(temp.path())?;
291
292 Package::load(manifest, temp, strictness, walker_factory)
293 }
294
295 pub fn from_manifest(wasmer_toml: impl AsRef<Path>) -> Result<Self, WasmerPackageError> {
297 Package::from_manifest_with_walker(wasmer_toml, wasmer_ignore_walker())
298 }
299
300 pub fn from_manifest_with_walker(
302 wasmer_toml: impl AsRef<Path>,
303 walker_factory: WalkBuilderFactory,
304 ) -> Result<Self, WasmerPackageError> {
305 Package::from_manifest_with_strictness(wasmer_toml, Strictness::default(), walker_factory)
306 }
307
308 pub fn from_manifest_with_strictness(
310 wasmer_toml: impl AsRef<Path>,
311 strictness: Strictness,
312 walker_factory: WalkBuilderFactory,
313 ) -> Result<Self, WasmerPackageError> {
314 let path = wasmer_toml.as_ref();
315 let path = path
316 .canonicalize()
317 .map_err(|error| WasmerPackageError::Canonicalize {
318 path: path.to_path_buf(),
319 error,
320 })?;
321
322 let wasmer_toml =
323 std::fs::read_to_string(&path).map_err(|error| WasmerPackageError::FileRead {
324 path: path.to_path_buf(),
325 error,
326 })?;
327 let wasmer_toml: WasmerManifest =
328 toml::from_str(&wasmer_toml).map_err(|error| WasmerPackageError::TomlDeserialize {
329 path: path.to_path_buf(),
330 error,
331 })?;
332
333 let base_dir = path
334 .parent()
335 .expect("Canonicalizing should always result in a file with a parent directory")
336 .to_path_buf();
337
338 for path in wasmer_toml.fs.values() {
339 if !base_dir.join(path).exists() {
340 return Err(WasmerPackageError::PathNotExists { path: path.clone() });
341 }
342 }
343
344 Package::load(wasmer_toml, base_dir, strictness, walker_factory)
345 }
346
347 pub fn from_json_manifest(manifest: PathBuf) -> Result<Self, WasmerPackageError> {
350 Self::from_json_manifest_with_strictness(manifest, Strictness::default())
351 }
352
353 pub fn from_json_manifest_with_strictness(
356 manifest: PathBuf,
357 strictness: Strictness,
358 ) -> Result<Self, WasmerPackageError> {
359 let base_dir = manifest
360 .parent()
361 .expect("Canonicalizing should always result in a file with a parent directory")
362 .to_path_buf();
363
364 let base_dir: BaseDir = base_dir.into();
365
366 let contents = std::fs::read(&manifest)?;
367 let manifest: WebcManifest =
368 serde_json::from_slice(&contents).map_err(|e| WasmerPackageError::JsonDeserialize {
369 path: manifest.clone(),
370 error: e,
371 })?;
372
373 let mut atoms = BTreeMap::<String, OwnedBuffer>::new();
374 for atom in manifest.atoms.keys() {
375 let path = base_dir.path().join(atom);
376
377 let contents = std::fs::read(&path)
378 .map_err(|e| WasmerPackageError::FileRead { path, error: e })?;
379
380 atoms.insert(atom.clone(), contents.into());
381 }
382
383 let mut volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>> =
384 BTreeMap::new();
385 if let Some(fs_mappings) = manifest.filesystem()? {
386 for entry in fs_mappings.iter() {
387 let mut dirs = BTreeSet::new();
388 let path = entry.volume_name.strip_prefix('/').ok_or_else(|| {
389 WasmerPackageError::MalformedPath(PathBuf::from(&entry.volume_name))
390 })?;
391 let path = base_dir.path().join(path);
392 dirs.insert(path);
393
394 volumes.insert(
395 entry.volume_name.clone(),
396 Arc::new(FsVolume::new(
397 entry.volume_name.clone(),
398 base_dir.path().to_owned(),
399 BTreeSet::new(),
400 dirs,
401 include_everything_walker(),
402 )),
403 );
404 }
405 }
406
407 let mut files = BTreeSet::new();
408 for entry in std::fs::read_dir(base_dir.path().join(FsVolume::METADATA))? {
409 let entry = entry?;
410
411 files.insert(entry.path());
412 }
413
414 if let Some(wapm) = manifest.wapm().unwrap() {
415 if let Some(license_file) = wapm.license_file.as_ref() {
416 let path = license_file.path.strip_prefix('/').ok_or_else(|| {
417 WasmerPackageError::MalformedPath(PathBuf::from(&license_file.path))
418 })?;
419 let path = base_dir.path().join(FsVolume::METADATA).join(path);
420
421 files.insert(path);
422 }
423
424 if let Some(readme_file) = wapm.readme.as_ref() {
425 let path = readme_file.path.strip_prefix('/').ok_or_else(|| {
426 WasmerPackageError::MalformedPath(PathBuf::from(&readme_file.path))
427 })?;
428 let path = base_dir.path().join(FsVolume::METADATA).join(path);
429
430 files.insert(path);
431 }
432 }
433
434 volumes.insert(
435 FsVolume::METADATA.to_string(),
436 Arc::new(FsVolume::new_with_intermediate_dirs(
437 FsVolume::METADATA.to_string(),
438 base_dir.path().join(FsVolume::METADATA).to_owned(),
439 files,
440 BTreeSet::new(),
441 include_everything_walker(),
442 )),
443 );
444
445 Ok(Package {
446 base_dir,
447 manifest,
448 atoms,
449 strictness,
450 volumes,
451 })
452 }
453
454 pub fn from_in_memory(
456 manifest: WasmerManifest,
457 volumes: BTreeMap<String, MemoryVolume>,
458 atoms: BTreeMap<String, (Option<String>, OwnedBuffer)>,
459 metadata: MemoryVolume,
460 strictness: Strictness,
461 ) -> Result<Self, WasmerPackageError> {
462 let mut new_volumes = BTreeMap::new();
463
464 for (k, v) in volumes.into_iter() {
465 new_volumes.insert(k, Arc::new(v) as _);
466 }
467
468 new_volumes.insert(MemoryVolume::METADATA.to_string(), Arc::new(metadata) as _);
469
470 let volumes = new_volumes;
471
472 let mut annotated_atoms = BTreeMap::new();
473
474 for (atom_name, (a, b)) in atoms {
475 let annotations =
476 if let Some(module) = manifest.modules.iter().find(|v| v.name == atom_name) {
477 module.annotations.as_ref()
478 } else {
479 None
480 };
481
482 annotated_atoms.insert(atom_name, (a, b, annotations));
483 }
484
485 let (mut manifest, atoms) =
486 super::manifest::in_memory_wasmer_manifest_to_webc(&manifest, &annotated_atoms)?;
487
488 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
489 let mut wapm: Wapm = entry.deserialized()?;
490
491 wapm.name.take();
492 wapm.version.take();
493 wapm.description.take();
494
495 *entry = ciborium::value::Value::serialized(&wapm)?;
496 };
497
498 Ok(Package {
499 base_dir: BaseDir::Path(Path::new("/").to_path_buf()),
500 manifest,
501 atoms,
502 strictness,
503 volumes,
504 })
505 }
506
507 fn load(
508 wasmer_toml: WasmerManifest,
509 base_dir: impl Into<BaseDir>,
510 strictness: Strictness,
511 walker_factory: WalkBuilderFactory,
512 ) -> Result<Self, WasmerPackageError> {
513 let base_dir = base_dir.into();
514
515 if strictness.is_strict() {
516 wasmer_toml.validate()?;
517 }
518
519 let (mut manifest, atoms) =
520 wasmer_manifest_to_webc(&wasmer_toml, base_dir.path(), strictness)?;
521
522 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
524 let mut wapm: Wapm = entry.deserialized()?;
525
526 wapm.name.take();
527 wapm.version.take();
528 wapm.description.take();
529
530 *entry = ciborium::value::Value::serialized(&wapm)?;
531 };
532
533 let base_dir_path = base_dir.path().to_path_buf();
535 let metadata_volume = FsVolume::new_metadata(&wasmer_toml, base_dir_path.clone())?;
537 let mut volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>> = {
539 let old = FsVolume::new_assets(&wasmer_toml, &base_dir_path, walker_factory)?;
540 let mut new = BTreeMap::new();
541
542 for (k, v) in old.into_iter() {
543 new.insert(k, Arc::new(v) as _);
544 }
545
546 new
547 };
548 volumes.insert(
549 metadata_volume.name().to_string(),
550 Arc::new(metadata_volume),
551 );
552
553 Ok(Package {
554 base_dir,
555 manifest,
556 atoms,
557 strictness,
558 volumes,
559 })
560 }
561
562 pub fn webc_hash(&self) -> Option<[u8; 32]> {
564 None
565 }
566
567 pub fn manifest(&self) -> &WebcManifest {
569 &self.manifest
570 }
571
572 pub fn atoms(&self) -> &BTreeMap<String, OwnedBuffer> {
574 &self.atoms
575 }
576
577 pub fn volumes(
579 &self,
580 ) -> impl Iterator<Item = &Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
581 self.volumes.values()
582 }
583
584 pub fn serialize(&self) -> Result<Bytes, Error> {
587 let mut w = Writer::new(ChecksumAlgorithm::Sha256)
588 .write_manifest(self.manifest())?
589 .write_atoms(self.atom_entries()?)?;
590
591 for (name, volume) in &self.volumes {
592 w.write_volume(name.as_str(), volume.as_directory_tree(self.strictness)?)?;
593 }
594
595 let serialized = w.finish(webc::v3::SignatureAlgorithm::None)?;
596
597 Ok(serialized)
598 }
599
600 fn atom_entries(&self) -> Result<BTreeMap<PathSegment, FileEntry<'_>>, Error> {
601 self.atoms()
602 .iter()
603 .map(|(key, value)| {
604 let filename = PathSegment::parse(key)
605 .with_context(|| format!("\"{key}\" isn't a valid atom name"))?;
606 Ok((filename, FileEntry::borrowed(value, Timestamps::default())))
608 })
609 .collect()
610 }
611
612 pub(crate) fn get_volume(
613 &self,
614 name: &str,
615 ) -> Option<Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
616 self.volumes.get(name).cloned()
617 }
618
619 pub(crate) fn volume_names(&self) -> Vec<Cow<'_, str>> {
620 self.volumes
621 .keys()
622 .map(|name| Cow::Borrowed(name.as_str()))
623 .collect()
624 }
625}
626
627impl AbstractWebc for Package {
628 fn version(&self) -> Version {
629 Version::V3
630 }
631
632 fn manifest(&self) -> &WebcManifest {
633 self.manifest()
634 }
635
636 fn atom_names(&self) -> Vec<Cow<'_, str>> {
637 self.atoms()
638 .keys()
639 .map(|s| Cow::Borrowed(s.as_str()))
640 .collect()
641 }
642
643 fn get_atom(&self, name: &str) -> Option<OwnedBuffer> {
644 self.atoms().get(name).cloned()
645 }
646
647 fn get_webc_hash(&self) -> Option<[u8; 32]> {
648 self.webc_hash()
649 }
650
651 fn get_atoms_hash(&self) -> Option<[u8; 32]> {
652 None
653 }
654
655 fn volume_names(&self) -> Vec<Cow<'_, str>> {
656 self.volume_names()
657 }
658
659 fn get_volume(&self, name: &str) -> Option<Volume> {
660 self.get_volume(name).map(|v| {
661 let a: Arc<dyn AbstractVolume + Send + Sync + 'static> = v.as_volume();
662
663 Volume::from(a)
664 })
665 }
666}
667
668impl From<Package> for Container {
669 fn from(value: Package) -> Self {
670 Container::new(value)
671 }
672}
673
674const IS_WASI: bool = cfg!(all(target_family = "wasm", target_os = "wasi"));
675
676fn tempdir() -> Result<TempDir, std::io::Error> {
684 if !IS_WASI {
685 return TempDir::new();
687 }
688
689 let temp_dir: PathBuf = std::env::var("TMPDIR")
692 .unwrap_or_else(|_| "/tmp".to_string())
693 .into();
694
695 if temp_dir.exists() {
696 TempDir::new_in(temp_dir)
697 } else {
698 if let Ok(current_exe) = std::env::current_exe()
704 && let Some(parent) = current_exe.parent()
705 && let Ok(temp) = TempDir::new_in(parent)
706 {
707 return Ok(temp);
708 }
709
710 std::fs::create_dir_all(&temp_dir)?;
712 TempDir::new_in(temp_dir)
713 }
714}
715
716fn unpack_archive(
723 mut archive: Archive<impl std::io::Read>,
724 dest: &Path,
725) -> Result<(), std::io::Error> {
726 cfg_if::cfg_if! {
727 if #[cfg(all(target_family = "wasm", target_os = "wasi"))]
728 {
729 for entry in archive.entries()? {
732 let mut entry = entry?;
733 let item_path = entry.path()?;
734 let full_path = resolve_archive_path(dest, &item_path);
735
736 match entry.header().entry_type() {
737 tar::EntryType::Directory => {
738 std::fs::create_dir_all(&full_path)?;
739 }
740 tar::EntryType::Regular => {
741 if let Some(parent) = full_path.parent() {
742 std::fs::create_dir_all(parent)?;
743 }
744 let mut f = File::create(&full_path)?;
745 std::io::copy(&mut entry, &mut f)?;
746
747 let mtime = entry.header().mtime().unwrap_or_default();
748 if let Err(e) = set_timestamp(full_path.as_path(), mtime) {
749 println!("WARN: {e:?}");
750 }
751 }
752 _ => {}
753 }
754 }
755 Ok(())
756
757 } else {
758 archive.unpack(dest)
759 }
760 }
761}
762
763#[cfg(all(target_family = "wasm", target_os = "wasi"))]
764fn set_timestamp(path: &Path, timestamp: u64) -> Result<(), anyhow::Error> {
765 let fd = unsafe {
766 libc::open(
767 path.as_os_str().as_encoded_bytes().as_ptr() as _,
768 libc::O_RDONLY,
769 )
770 };
771
772 if fd < 0 {
773 anyhow::bail!(format!("failed to open: {}", path.display()));
774 }
775
776 let timespec = [
777 libc::timespec {
779 tv_sec: unsafe { libc::time(std::ptr::null_mut()) }, tv_nsec: 0,
781 },
782 libc::timespec {
784 tv_sec: timestamp as i64,
785 tv_nsec: 0,
786 },
787 ];
788
789 let res = unsafe { libc::futimens(fd, timespec.as_ptr() as _) };
790
791 if res < 0 {
792 anyhow::bail!("failed to set timestamp for: {}", path.display());
793 }
794
795 Ok(())
796}
797
798#[cfg(all(target_family = "wasm", target_os = "wasi"))]
799fn resolve_archive_path(base_dir: &Path, path: &Path) -> PathBuf {
800 let mut buffer = base_dir.to_path_buf();
801
802 for component in path.components() {
803 match component {
804 std::path::Component::Prefix(_)
805 | std::path::Component::RootDir
806 | std::path::Component::CurDir => continue,
807 std::path::Component::ParentDir => {
808 buffer.pop();
809 }
810 std::path::Component::Normal(segment) => {
811 buffer.push(segment);
812 }
813 }
814 }
815
816 buffer
817}
818
819fn read_manifest(base_dir: &Path) -> Result<(PathBuf, WasmerManifest), WasmerPackageError> {
820 for path in ["wasmer.toml", "wapm.toml"] {
821 let path = base_dir.join(path);
822
823 match std::fs::read_to_string(&path) {
824 Ok(s) => {
825 let toml_file = toml::from_str(&s).map_err({
826 let path = path.clone();
827 |error| WasmerPackageError::TomlDeserialize { path, error }
828 })?;
829
830 return Ok((path, toml_file));
831 }
832 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
833 Err(error) => {
834 return Err(WasmerPackageError::FileRead { path, error });
835 }
836 }
837 }
838
839 Err(WasmerPackageError::MissingManifest)
840}
841
842#[derive(Debug)]
843enum BaseDir {
844 Path(PathBuf),
846 Temp(TempDir),
848}
849
850impl BaseDir {
851 fn path(&self) -> &Path {
852 match self {
853 BaseDir::Path(p) => p.as_path(),
854 BaseDir::Temp(t) => t.path(),
855 }
856 }
857}
858
859impl From<TempDir> for BaseDir {
860 fn from(v: TempDir) -> Self {
861 Self::Temp(v)
862 }
863}
864
865impl From<PathBuf> for BaseDir {
866 fn from(v: PathBuf) -> Self {
867 Self::Path(v)
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use std::{
874 collections::BTreeMap,
875 fs::File,
876 path::{Path, PathBuf},
877 str::FromStr,
878 time::SystemTime,
879 };
880
881 use flate2::{Compression, write::GzEncoder};
882 use sha2::Digest;
883 use shared_buffer::OwnedBuffer;
884 use tempfile::TempDir;
885 use webc::{
886 PathSegment, PathSegments,
887 metadata::{
888 Binding, BindingsExtended, WaiBindings, WitBindings,
889 annotations::{FileSystemMapping, VolumeSpecificPath},
890 },
891 };
892
893 use crate::{package::*, utils::from_bytes};
894
895 #[test]
896 fn nonexistent_files() {
897 let temp = TempDir::new().unwrap();
898
899 assert!(Package::from_manifest(temp.path().join("nonexistent.toml")).is_err());
900 assert!(Package::from_tarball_file(temp.path().join("nonexistent.tar.gz")).is_err());
901 }
902
903 #[test]
904 fn load_a_tarball() {
905 let coreutils = Path::new(env!("CARGO_MANIFEST_DIR"))
906 .join("..")
907 .join("..")
908 .join("tests")
909 .join("old-tar-gz")
910 .join("coreutils-1.0.11.tar.gz");
911 assert!(coreutils.exists());
912
913 let package = Package::from_tarball_file(coreutils).unwrap();
914
915 let wapm = package.manifest().wapm().unwrap().unwrap();
916 assert!(wapm.name.is_none());
917 assert!(wapm.version.is_none());
918 assert!(wapm.description.is_none());
919 }
920
921 #[test]
922 fn tarball_with_no_manifest() {
923 let temp = TempDir::new().unwrap();
924 let empty_tarball = temp.path().join("empty.tar.gz");
925 let mut f = File::create(&empty_tarball).unwrap();
926 tar::Builder::new(GzEncoder::new(&mut f, Compression::fast()))
927 .finish()
928 .unwrap();
929
930 assert!(Package::from_tarball_file(&empty_tarball).is_err());
931 }
932
933 #[test]
934 fn empty_package_on_disk() {
935 let temp = TempDir::new().unwrap();
936 let manifest = temp.path().join("wasmer.toml");
937 std::fs::write(
938 &manifest,
939 r#"
940 [package]
941 name = "some/package"
942 version = "0.0.0"
943 description = "A dummy package"
944 "#,
945 )
946 .unwrap();
947
948 let package = Package::from_manifest(&manifest).unwrap();
949
950 let wapm = package.manifest().wapm().unwrap().unwrap();
951 assert!(wapm.name.is_none());
952 assert!(wapm.version.is_none());
953 assert!(wapm.description.is_none());
954 }
955
956 #[test]
957 fn load_old_cowsay() {
958 let tarball = Path::new(env!("CARGO_MANIFEST_DIR"))
959 .join("..")
960 .join("..")
961 .join("tests")
962 .join("old-tar-gz")
963 .join("cowsay-0.3.0.tar.gz");
964
965 let pkg = Package::from_tarball_file(tarball).unwrap();
966
967 insta::assert_yaml_snapshot!(pkg.manifest());
968 assert_eq!(
969 pkg.manifest.commands.keys().collect::<Vec<_>>(),
970 ["cowsay", "cowthink"],
971 );
972 }
973
974 #[test]
975 fn serialize_package_with_non_existent_fs() {
976 let temp = TempDir::new().unwrap();
977 let wasmer_toml = r#"
978 [package]
979 name = "some/package"
980 version = "0.0.0"
981 description = "Test package"
982
983 [fs]
984 "/first" = "./first"
985 "#;
986 let manifest = temp.path().join("wasmer.toml");
987
988 std::fs::write(&manifest, wasmer_toml).unwrap();
989
990 let error = Package::from_manifest(manifest).unwrap_err();
991
992 match error {
993 WasmerPackageError::PathNotExists { path } => {
994 assert_eq!(path, PathBuf::from_str("./first").unwrap());
995 }
996 e => panic!("unexpected error: {e:?}"),
997 }
998 }
999
1000 #[test]
1001 fn serialize_package_with_bundled_directories() {
1002 let temp = TempDir::new().unwrap();
1003 let wasmer_toml = r#"
1004 [package]
1005 name = "some/package"
1006 version = "0.0.0"
1007 description = "Test package"
1008
1009 [fs]
1010 "/first" = "first"
1011 second = "nested/dir"
1012 "second/child" = "third"
1013 empty = "empty"
1014 "#;
1015 let manifest = temp.path().join("wasmer.toml");
1016 std::fs::write(&manifest, wasmer_toml).unwrap();
1017 let first = temp.path().join("first");
1035 std::fs::create_dir_all(&first).unwrap();
1036 std::fs::write(first.join("file.txt"), "File").unwrap();
1037 let second = temp.path().join("nested").join("dir");
1039 std::fs::create_dir_all(&second).unwrap();
1040 std::fs::write(second.join(".wasmerignore"), "ignore_me").unwrap();
1041 std::fs::write(second.join(".hidden"), "something something").unwrap();
1042 std::fs::write(second.join("ignore_me"), "something something").unwrap();
1043 std::fs::write(second.join("README.md"), "please").unwrap();
1044 let another_dir = temp.path().join("nested").join("dir").join("another-dir");
1045 std::fs::create_dir_all(&another_dir).unwrap();
1046 std::fs::write(another_dir.join("empty.txt"), "").unwrap();
1047 let third = temp.path().join("third");
1049 std::fs::create_dir_all(&third).unwrap();
1050 std::fs::write(third.join("file.txt"), "Hello, World!").unwrap();
1051 let empty_dir = temp.path().join("empty");
1053 std::fs::create_dir_all(empty_dir).unwrap();
1054
1055 let package = Package::from_manifest(manifest).unwrap();
1056
1057 let webc = package.serialize().unwrap();
1058 let webc = from_bytes(webc).unwrap();
1059 let manifest = webc.manifest();
1060 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1061 assert!(wapm_metadata.name.is_none());
1062 assert!(wapm_metadata.version.is_none());
1063 assert!(wapm_metadata.description.is_none());
1064 let fs_table = manifest.filesystem().unwrap().unwrap();
1065 assert_eq!(
1066 fs_table,
1067 [
1068 FileSystemMapping {
1069 from: None,
1070 volume_name: "/first".to_string(),
1071 host_path: None,
1072 mount_path: "/first".to_string(),
1073 },
1074 FileSystemMapping {
1075 from: None,
1076 volume_name: "/nested/dir".to_string(),
1077 host_path: None,
1078 mount_path: "/second".to_string(),
1079 },
1080 FileSystemMapping {
1081 from: None,
1082 volume_name: "/third".to_string(),
1083 host_path: None,
1084 mount_path: "/second/child".to_string(),
1085 },
1086 FileSystemMapping {
1087 from: None,
1088 volume_name: "/empty".to_string(),
1089 host_path: None,
1090 mount_path: "/empty".to_string(),
1091 },
1092 ]
1093 );
1094
1095 let first_file_hash: [u8; 32] = sha2::Sha256::digest(b"File").into();
1096 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"please").into();
1097 let empty_hash: [u8; 32] = sha2::Sha256::digest(b"").into();
1098 let third_file_hash: [u8; 32] = sha2::Sha256::digest(b"Hello, World!").into();
1099
1100 let first_volume = webc.get_volume("/first").unwrap();
1101 assert_eq!(
1102 first_volume.read_file("/file.txt").unwrap(),
1103 (b"File".as_slice().into(), Some(first_file_hash)),
1104 );
1105
1106 let nested_dir_volume = webc.get_volume("/nested/dir").unwrap();
1107 assert_eq!(
1108 nested_dir_volume.read_file("README.md").unwrap(),
1109 (b"please".as_slice().into(), Some(readme_hash)),
1110 );
1111 assert!(nested_dir_volume.read_file(".wasmerignore").is_some());
1112 assert!(nested_dir_volume.read_file(".hidden").is_some());
1113 assert!(nested_dir_volume.read_file("ignore_me").is_none());
1114 assert_eq!(
1115 nested_dir_volume
1116 .read_file("/another-dir/empty.txt")
1117 .unwrap(),
1118 (b"".as_slice().into(), Some(empty_hash))
1119 );
1120
1121 let third_volume = webc.get_volume("/third").unwrap();
1122 assert_eq!(
1123 third_volume.read_file("/file.txt").unwrap(),
1124 (b"Hello, World!".as_slice().into(), Some(third_file_hash))
1125 );
1126
1127 let empty_volume = webc.get_volume("/empty").unwrap();
1128 assert_eq!(
1129 empty_volume.read_dir("/").unwrap().len(),
1130 0,
1131 "Directories should be included, even if empty"
1132 );
1133 }
1134
1135 #[test]
1136 fn serialize_package_with_metadata_files() {
1137 let temp = TempDir::new().unwrap();
1138 let wasmer_toml = r#"
1139 [package]
1140 name = "some/package"
1141 version = "0.0.0"
1142 description = "Test package"
1143 readme = "README.md"
1144 license-file = "LICENSE"
1145 "#;
1146 let manifest = temp.path().join("wasmer.toml");
1147 std::fs::write(&manifest, wasmer_toml).unwrap();
1148 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1149 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1150
1151 let serialized = Package::from_manifest(manifest)
1152 .unwrap()
1153 .serialize()
1154 .unwrap();
1155
1156 let webc = from_bytes(serialized).unwrap();
1157 let metadata_volume = webc.get_volume("metadata").unwrap();
1158
1159 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1160 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1161
1162 assert_eq!(
1163 metadata_volume.read_file("/README.md").unwrap(),
1164 (b"readme".as_slice().into(), Some(readme_hash))
1165 );
1166 assert_eq!(
1167 metadata_volume.read_file("/LICENSE").unwrap(),
1168 (b"license".as_slice().into(), Some(license_hash))
1169 );
1170 }
1171
1172 #[test]
1173 fn load_package_with_wit_bindings() {
1174 let temp = TempDir::new().unwrap();
1175 let wasmer_toml = r#"
1176 [package]
1177 name = "some/package"
1178 version = "0.0.0"
1179 description = ""
1180
1181 [[module]]
1182 name = "my-lib"
1183 source = "./my-lib.wasm"
1184 abi = "none"
1185 bindings = { wit-bindgen = "0.1.0", wit-exports = "./file.wit" }
1186 "#;
1187 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1188 std::fs::write(temp.path().join("file.wit"), "file").unwrap();
1189 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1190
1191 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1192 .unwrap()
1193 .serialize()
1194 .unwrap();
1195 let webc = from_bytes(package).unwrap();
1196
1197 assert_eq!(
1198 webc.manifest().bindings,
1199 vec![Binding {
1200 name: "library-bindings".to_string(),
1201 kind: "wit@0.1.0".to_string(),
1202 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wit(
1203 WitBindings {
1204 exports: "metadata://file.wit".to_string(),
1205 module: "my-lib".to_string(),
1206 }
1207 ))
1208 .unwrap(),
1209 }]
1210 );
1211 let metadata_volume = webc.get_volume("metadata").unwrap();
1212 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1213 assert_eq!(
1214 metadata_volume.read_file("/file.wit").unwrap(),
1215 (b"file".as_slice().into(), Some(file_hash))
1216 );
1217 insta::with_settings! {
1218 { description => wasmer_toml },
1219 { insta::assert_yaml_snapshot!(webc.manifest()); }
1220 }
1221 }
1222
1223 #[test]
1224 fn load_package_with_wai_bindings() {
1225 let temp = TempDir::new().unwrap();
1226 let wasmer_toml = r#"
1227 [package]
1228 name = "some/package"
1229 version = "0.0.0"
1230 description = ""
1231
1232 [[module]]
1233 name = "my-lib"
1234 source = "./my-lib.wasm"
1235 abi = "none"
1236 bindings = { wai-version = "0.2.0", exports = "./file.wai", imports = ["a.wai", "b.wai"] }
1237 "#;
1238 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1239 std::fs::write(temp.path().join("file.wai"), "file").unwrap();
1240 std::fs::write(temp.path().join("a.wai"), "a").unwrap();
1241 std::fs::write(temp.path().join("b.wai"), "b").unwrap();
1242 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1243
1244 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1245 .unwrap()
1246 .serialize()
1247 .unwrap();
1248 let webc = from_bytes(package).unwrap();
1249
1250 assert_eq!(
1251 webc.manifest().bindings,
1252 vec![Binding {
1253 name: "library-bindings".to_string(),
1254 kind: "wai@0.2.0".to_string(),
1255 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wai(
1256 WaiBindings {
1257 exports: Some("metadata://file.wai".to_string()),
1258 module: "my-lib".to_string(),
1259 imports: vec![
1260 "metadata://a.wai".to_string(),
1261 "metadata://b.wai".to_string(),
1262 ]
1263 }
1264 ))
1265 .unwrap(),
1266 }]
1267 );
1268 let metadata_volume = webc.get_volume("metadata").unwrap();
1269
1270 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1271 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1272 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1273
1274 assert_eq!(
1275 metadata_volume.read_file("/file.wai").unwrap(),
1276 (b"file".as_slice().into(), Some(file_hash))
1277 );
1278 assert_eq!(
1279 metadata_volume.read_file("/a.wai").unwrap(),
1280 (b"a".as_slice().into(), Some(a_hash))
1281 );
1282 assert_eq!(
1283 metadata_volume.read_file("/b.wai").unwrap(),
1284 (b"b".as_slice().into(), Some(b_hash))
1285 );
1286 insta::with_settings! {
1287 { description => wasmer_toml },
1288 { insta::assert_yaml_snapshot!(webc.manifest()); }
1289 }
1290 }
1291
1292 #[test]
1294 fn absolute_paths_in_wasmer_toml_issue_105() {
1295 let temp = TempDir::new().unwrap();
1296 let base_dir = temp.path().canonicalize().unwrap();
1297 let sep = std::path::MAIN_SEPARATOR;
1298 let wasmer_toml = format!(
1299 r#"
1300 [package]
1301 name = 'some/package'
1302 version = '0.0.0'
1303 description = 'Test package'
1304 readme = '{BASE_DIR}{sep}README.md'
1305 license-file = '{BASE_DIR}{sep}LICENSE'
1306
1307 [[module]]
1308 name = 'first'
1309 source = '{BASE_DIR}{sep}target{sep}debug{sep}package.wasm'
1310 bindings = {{ wai-version = '0.2.0', exports = '{BASE_DIR}{sep}bindings{sep}file.wai', imports = ['{BASE_DIR}{sep}bindings{sep}a.wai'] }}
1311 "#,
1312 BASE_DIR = base_dir.display(),
1313 );
1314 let manifest = temp.path().join("wasmer.toml");
1315 std::fs::write(&manifest, &wasmer_toml).unwrap();
1316 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1317 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1318 let bindings = temp.path().join("bindings");
1319 std::fs::create_dir_all(&bindings).unwrap();
1320 std::fs::write(bindings.join("file.wai"), "file.wai").unwrap();
1321 std::fs::write(bindings.join("a.wai"), "a.wai").unwrap();
1322 let target = temp.path().join("target").join("debug");
1323 std::fs::create_dir_all(&target).unwrap();
1324 std::fs::write(target.join("package.wasm"), b"\0asm...").unwrap();
1325
1326 let serialized = Package::from_manifest(manifest)
1327 .unwrap()
1328 .serialize()
1329 .unwrap();
1330
1331 let webc = from_bytes(serialized).unwrap();
1332 let manifest = webc.manifest();
1333 let wapm = manifest.wapm().unwrap().unwrap();
1334
1335 let lookup = |item: VolumeSpecificPath| {
1337 let volume = webc.get_volume(&item.volume).unwrap();
1338 let (contents, _) = volume.read_file(&item.path).unwrap();
1339 String::from_utf8(contents.into()).unwrap()
1340 };
1341 assert_eq!(lookup(wapm.license_file.unwrap()), "license");
1342 assert_eq!(lookup(wapm.readme.unwrap()), "readme");
1343
1344 let lookup = |item: &str| {
1347 let (volume, path) = item.split_once(":/").unwrap();
1348 let volume = webc.get_volume(volume).unwrap();
1349 let (content, _) = volume.read_file(path).unwrap();
1350 String::from_utf8(content.into()).unwrap()
1351 };
1352 let bindings = manifest.bindings[0].get_wai_bindings().unwrap();
1353 assert_eq!(lookup(&bindings.imports[0]), "a.wai");
1354 assert_eq!(lookup(bindings.exports.unwrap().as_str()), "file.wai");
1355
1356 let mut settings = insta::Settings::clone_current();
1358 let base_dir = base_dir.display().to_string();
1359 settings.set_description(wasmer_toml.replace(&base_dir, "[BASE_DIR]"));
1360 let filter = regex::escape(&base_dir);
1361 settings.add_filter(&filter, "[BASE_DIR]");
1362 settings.bind(|| {
1363 insta::assert_yaml_snapshot!(webc.manifest());
1364 });
1365 }
1366
1367 #[test]
1368 fn serializing_will_skip_missing_metadata_by_default() {
1369 let temp = TempDir::new().unwrap();
1370 let wasmer_toml = r#"
1371 [package]
1372 name = 'some/package'
1373 version = '0.0.0'
1374 description = 'Test package'
1375 readme = '/this/does/not/exist/README.md'
1376 license-file = 'LICENSE.wtf'
1377 "#;
1378 let manifest = temp.path().join("wasmer.toml");
1379 std::fs::write(&manifest, wasmer_toml).unwrap();
1380 let pkg = Package::from_manifest(manifest).unwrap();
1381
1382 let serialized = pkg.serialize().unwrap();
1383
1384 let webc = from_bytes(serialized).unwrap();
1385 let manifest = webc.manifest();
1386 let wapm = manifest.wapm().unwrap().unwrap();
1387 assert!(wapm.license_file.is_none());
1389 assert!(wapm.readme.is_none());
1390
1391 let pkg = Package {
1393 strictness: Strictness::Strict,
1394 ..pkg
1395 };
1396 assert!(pkg.serialize().is_err());
1397 }
1398
1399 #[test]
1400 fn serialize_package_without_local_base_fs_paths() {
1401 let temp = TempDir::new().unwrap();
1402 let wasmer_toml = r#"
1403 [package]
1404 name = "some/package"
1405 version = "0.0.0"
1406 description = "Test package"
1407 readme = 'README.md'
1408 license-file = 'LICENSE'
1409
1410 [fs]
1411 "/path_in_wasix" = "local-dir/dir1"
1412 "#;
1413 let manifest = temp.path().join("wasmer.toml");
1414 std::fs::write(&manifest, wasmer_toml).unwrap();
1415
1416 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1417 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1418
1419 let dir1 = temp.path().join("local-dir").join("dir1");
1426 std::fs::create_dir_all(&dir1).unwrap();
1427
1428 let a = dir1.join("a");
1429 let b = dir1.join("b");
1430
1431 std::fs::write(a, "a").unwrap();
1432 std::fs::write(b, "b").unwrap();
1433
1434 let package = Package::from_manifest(manifest).unwrap();
1435
1436 let webc = package.serialize().unwrap();
1437 let webc = from_bytes(webc).unwrap();
1438 let manifest = webc.manifest();
1439 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1440
1441 assert!(wapm_metadata.name.is_none());
1442 assert!(wapm_metadata.version.is_none());
1443 assert!(wapm_metadata.description.is_none());
1444
1445 let fs_table = manifest.filesystem().unwrap().unwrap();
1446 assert_eq!(
1447 fs_table,
1448 [FileSystemMapping {
1449 from: None,
1450 volume_name: "/local-dir/dir1".to_string(),
1451 host_path: None,
1452 mount_path: "/path_in_wasix".to_string(),
1453 },]
1454 );
1455
1456 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1457 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1458
1459 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1460 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1461
1462 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1463 let meta_volume = webc.get_volume("metadata").unwrap();
1464
1465 assert_eq!(
1466 meta_volume.read_file("LICENSE").unwrap(),
1467 (b"license".as_slice().into(), Some(license_hash)),
1468 );
1469 assert_eq!(
1470 meta_volume.read_file("README.md").unwrap(),
1471 (b"readme".as_slice().into(), Some(readme_hash)),
1472 );
1473 assert_eq!(
1474 dir1_volume.read_file("a").unwrap(),
1475 (b"a".as_slice().into(), Some(a_hash))
1476 );
1477 assert_eq!(
1478 dir1_volume.read_file("b").unwrap(),
1479 (b"b".as_slice().into(), Some(b_hash))
1480 );
1481 }
1482
1483 #[test]
1484 fn serialize_package_with_nested_fs_entries_without_local_base_fs_paths() {
1485 let temp = TempDir::new().unwrap();
1486 let wasmer_toml = r#"
1487 [package]
1488 name = "some/package"
1489 version = "0.0.0"
1490 description = "Test package"
1491 readme = 'README.md'
1492 license-file = 'LICENSE'
1493
1494 [fs]
1495 "/path_in_wasix" = "local-dir/dir1"
1496 "#;
1497 let manifest = temp.path().join("wasmer.toml");
1498 std::fs::write(&manifest, wasmer_toml).unwrap();
1499
1500 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1501 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1502
1503 let local_dir = temp.path().join("local-dir");
1511 std::fs::create_dir_all(&local_dir).unwrap();
1512
1513 let dir1 = local_dir.join("dir1");
1514 std::fs::create_dir_all(&dir1).unwrap();
1515
1516 let dir2 = dir1.join("dir2");
1517 std::fs::create_dir_all(&dir2).unwrap();
1518
1519 let a = dir2.join("a");
1520 let b = dir1.join("b");
1521
1522 std::fs::write(a, "a").unwrap();
1523 std::fs::write(b, "b").unwrap();
1524
1525 let package = Package::from_manifest(manifest).unwrap();
1526
1527 let webc = package.serialize().unwrap();
1528 let webc = from_bytes(webc).unwrap();
1529 let manifest = webc.manifest();
1530 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1531
1532 assert!(wapm_metadata.name.is_none());
1533 assert!(wapm_metadata.version.is_none());
1534 assert!(wapm_metadata.description.is_none());
1535
1536 let fs_table = manifest.filesystem().unwrap().unwrap();
1537 assert_eq!(
1538 fs_table,
1539 [FileSystemMapping {
1540 from: None,
1541 volume_name: "/local-dir/dir1".to_string(),
1542 host_path: None,
1543 mount_path: "/path_in_wasix".to_string(),
1544 },]
1545 );
1546
1547 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1548 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1549
1550 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1551 let dir2_hash: [u8; 32] = sha2::Sha256::digest(a_hash).into();
1552 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1553
1554 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1555 let meta_volume = webc.get_volume("metadata").unwrap();
1556
1557 assert_eq!(
1558 meta_volume.read_file("LICENSE").unwrap(),
1559 (b"license".as_slice().into(), Some(license_hash)),
1560 );
1561 assert_eq!(
1562 meta_volume.read_file("README.md").unwrap(),
1563 (b"readme".as_slice().into(), Some(readme_hash)),
1564 );
1565 assert_eq!(
1566 dir1_volume
1567 .read_dir("/")
1568 .unwrap()
1569 .into_iter()
1570 .map(|(p, h, _)| (p, h))
1571 .collect::<Vec<_>>(),
1572 vec![
1573 (PathSegment::parse("b").unwrap(), Some(b_hash)),
1574 (PathSegment::parse("dir2").unwrap(), Some(dir2_hash))
1575 ]
1576 );
1577 assert_eq!(
1578 dir1_volume
1579 .read_dir("/dir2")
1580 .unwrap()
1581 .into_iter()
1582 .map(|(p, h, _)| (p, h))
1583 .collect::<Vec<_>>(),
1584 vec![(PathSegment::parse("a").unwrap(), Some(a_hash))]
1585 );
1586 assert_eq!(
1587 dir1_volume.read_file("/dir2/a").unwrap(),
1588 (b"a".as_slice().into(), Some(a_hash))
1589 );
1590 assert_eq!(
1591 dir1_volume.read_file("/b").unwrap(),
1592 (b"b".as_slice().into(), Some(b_hash))
1593 );
1594 }
1595
1596 #[test]
1597 fn serialize_package_mapped_to_same_dir_without_local_base_fs_paths() {
1598 let temp = TempDir::new().unwrap();
1599 let wasmer_toml = r#"
1600 [package]
1601 name = "some/package"
1602 version = "0.0.0"
1603 description = "Test package"
1604 readme = 'README.md'
1605 license-file = 'LICENSE'
1606
1607 [fs]
1608 "/dir1" = "local-dir1/dir"
1609 "/dir2" = "local-dir2/dir"
1610 "#;
1611 let manifest = temp.path().join("wasmer.toml");
1612 std::fs::write(&manifest, wasmer_toml).unwrap();
1613
1614 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1615 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1616
1617 let dir1 = temp.path().join("local-dir1").join("dir");
1624 std::fs::create_dir_all(&dir1).unwrap();
1625 let dir2 = temp.path().join("local-dir2").join("dir");
1626 std::fs::create_dir_all(&dir2).unwrap();
1627
1628 let package = Package::from_manifest(manifest).unwrap();
1629
1630 let webc = package.serialize().unwrap();
1631 let webc = from_bytes(webc).unwrap();
1632 let manifest = webc.manifest();
1633 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1634
1635 assert!(wapm_metadata.name.is_none());
1636 assert!(wapm_metadata.version.is_none());
1637 assert!(wapm_metadata.description.is_none());
1638
1639 let fs_table = manifest.filesystem().unwrap().unwrap();
1640 assert_eq!(
1641 fs_table,
1642 [
1643 FileSystemMapping {
1644 from: None,
1645 volume_name: "/local-dir1/dir".to_string(),
1646 host_path: None,
1647 mount_path: "/dir1".to_string(),
1648 },
1649 FileSystemMapping {
1650 from: None,
1651 volume_name: "/local-dir2/dir".to_string(),
1652 host_path: None,
1653 mount_path: "/dir2".to_string(),
1654 },
1655 ]
1656 );
1657
1658 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1659 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1660
1661 let dir1_volume = webc.get_volume("/local-dir1/dir").unwrap();
1662 let dir2_volume = webc.get_volume("/local-dir2/dir").unwrap();
1663 let meta_volume = webc.get_volume("metadata").unwrap();
1664
1665 assert_eq!(
1666 meta_volume.read_file("LICENSE").unwrap(),
1667 (b"license".as_slice().into(), Some(license_hash)),
1668 );
1669 assert_eq!(
1670 meta_volume.read_file("README.md").unwrap(),
1671 (b"readme".as_slice().into(), Some(readme_hash)),
1672 );
1673 assert!(dir1_volume.read_dir("/").unwrap().is_empty());
1674 assert!(dir2_volume.read_dir("/").unwrap().is_empty());
1675 }
1676
1677 #[test]
1678 fn metadata_only_contains_relevant_files() {
1679 let temp = TempDir::new().unwrap();
1680 let wasmer_toml = r#"
1681 [package]
1682 name = "some/package"
1683 version = "0.0.0"
1684 description = ""
1685 license-file = "./path/to/LICENSE"
1686 readme = "README.md"
1687
1688 [[module]]
1689 name = "asdf"
1690 source = "asdf.wasm"
1691 abi = "none"
1692 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1693 "#;
1694
1695 let manifest = temp.path().join("wasmer.toml");
1696 std::fs::write(&manifest, wasmer_toml).unwrap();
1697
1698 let license_dir = temp.path().join("path").join("to");
1699 std::fs::create_dir_all(&license_dir).unwrap();
1700 std::fs::write(license_dir.join("LICENSE"), "license").unwrap();
1701 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1702 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1703 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1704 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1705 std::fs::write(temp.path().join("unwanted_file.txt"), "unwanted_file").unwrap();
1706
1707 let package = Package::from_manifest(manifest).unwrap();
1708
1709 let contents: Vec<_> = package
1710 .get_volume("metadata")
1711 .unwrap()
1712 .read_dir(&PathSegments::ROOT)
1713 .unwrap()
1714 .into_iter()
1715 .map(|(path, _, _)| path)
1716 .collect();
1717
1718 assert_eq!(
1719 contents,
1720 vec![
1721 PathSegment::parse("README.md").unwrap(),
1722 PathSegment::parse("asdf.wai").unwrap(),
1723 PathSegment::parse("browser.wai").unwrap(),
1724 PathSegment::parse("path").unwrap(),
1725 ]
1726 );
1727 }
1728
1729 #[test]
1730 fn create_from_in_memory() -> anyhow::Result<()> {
1731 let wasmer_toml = r#"
1732 [dependencies]
1733 "wasmer/python" = "3.12.9+build.9"
1734
1735
1736 [[command]]
1737 module = "wasmer/python:python"
1738 name = "hello"
1739 runner = "wasi"
1740
1741 [command.annotations.wasi]
1742 main-args = [ "-c", "import os; print([f for f in os.walk('/public')]); " ]
1743
1744 [fs]
1745 "/public" = "public"
1746 "#;
1747
1748 let manifest = toml::from_str(wasmer_toml)?;
1749
1750 let file_modified = SystemTime::now();
1751 let file_data = String::from("Hello, world!").as_bytes().to_vec();
1752
1753 let file = MemoryFile {
1754 modified: file_modified,
1755 data: file_data,
1756 };
1757
1758 let mut nodes = BTreeMap::new();
1759 nodes.insert(String::from("hello.txt"), MemoryNode::File(file));
1760
1761 let dir_modified = SystemTime::now();
1762 let dir = MemoryDir {
1763 modified: dir_modified,
1764 nodes,
1765 };
1766
1767 let volume = MemoryVolume { node: dir };
1768 let mut volumes = BTreeMap::new();
1769
1770 volumes.insert("public".to_string(), volume);
1771
1772 let atoms = BTreeMap::new();
1773 let package = super::Package::from_in_memory(
1774 manifest,
1775 volumes,
1776 atoms,
1777 MemoryVolume {
1778 node: MemoryDir {
1779 modified: SystemTime::now(),
1780 nodes: BTreeMap::new(),
1781 },
1782 },
1783 Strictness::Strict,
1784 )?;
1785
1786 _ = package.serialize()?;
1787
1788 Ok(())
1789 }
1790
1791 #[test]
1792 fn compare_fs_mem_manifest() -> anyhow::Result<()> {
1793 let wasmer_toml = r#"
1794 [package]
1795 name = "test"
1796 version = "0.0.0"
1797 description = "asdf"
1798 "#;
1799
1800 let temp = TempDir::new()?;
1801 let manifest_path = temp.path().join("wasmer.toml");
1802 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1803
1804 let fs_package = super::Package::from_manifest(manifest_path)?;
1805
1806 let manifest = toml::from_str(wasmer_toml)?;
1807 let memory_package = super::Package::from_in_memory(
1808 manifest,
1809 Default::default(),
1810 Default::default(),
1811 MemoryVolume {
1812 node: MemoryDir {
1813 modified: SystemTime::UNIX_EPOCH,
1814 nodes: BTreeMap::new(),
1815 },
1816 },
1817 Strictness::Lossy,
1818 )?;
1819
1820 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1821
1822 Ok(())
1823 }
1824
1825 #[test]
1826 fn compare_fs_mem_manifest_and_atoms() -> anyhow::Result<()> {
1827 let wasmer_toml = r#"
1828 [package]
1829 name = "test"
1830 version = "0.0.0"
1831 description = "asdf"
1832
1833 [[module]]
1834 name = "foo"
1835 source = "foo.wasm"
1836 abi = "wasi"
1837 "#;
1838
1839 let temp = TempDir::new()?;
1840 let manifest_path = temp.path().join("wasmer.toml");
1841 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1842
1843 let atom_path = temp.path().join("foo.wasm");
1844 std::fs::write(&atom_path, b"").unwrap();
1845
1846 let fs_package = super::Package::from_manifest(manifest_path)?;
1847
1848 let manifest = toml::from_str(wasmer_toml)?;
1849 let mut atoms = BTreeMap::new();
1850 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1851 let memory_package = super::Package::from_in_memory(
1852 manifest,
1853 Default::default(),
1854 atoms,
1855 MemoryVolume {
1856 node: MemoryDir {
1857 modified: SystemTime::UNIX_EPOCH,
1858 nodes: BTreeMap::new(),
1859 },
1860 },
1861 Strictness::Lossy,
1862 )?;
1863
1864 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1865
1866 Ok(())
1867 }
1868
1869 #[test]
1870 fn compare_fs_mem_volume() -> anyhow::Result<()> {
1871 let wasmer_toml = r#"
1872 [package]
1873 name = "test"
1874 version = "0.0.0"
1875 description = "asdf"
1876
1877 [[module]]
1878 name = "foo"
1879 source = "foo.wasm"
1880 abi = "wasi"
1881
1882 [fs]
1883 "/bar" = "bar"
1884 "#;
1885
1886 let temp = TempDir::new()?;
1887 let manifest_path = temp.path().join("wasmer.toml");
1888 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1889
1890 let atom_path = temp.path().join("foo.wasm");
1891 std::fs::write(&atom_path, b"").unwrap();
1892
1893 let bar = temp.path().join("bar");
1894 std::fs::create_dir(&bar).unwrap();
1895
1896 let baz = bar.join("baz");
1897 std::fs::write(&baz, b"abc")?;
1898
1899 let baz_metadata = std::fs::metadata(&baz)?;
1900
1901 let fs_package = super::Package::from_manifest(manifest_path)?;
1902
1903 let manifest = toml::from_str(wasmer_toml)?;
1904
1905 let mut atoms = BTreeMap::new();
1906 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1907
1908 let mut volumes = BTreeMap::new();
1909 volumes.insert(
1910 "/bar".to_owned(),
1911 MemoryVolume {
1912 node: MemoryDir {
1913 modified: SystemTime::UNIX_EPOCH,
1914 nodes: {
1915 let mut children = BTreeMap::new();
1916
1917 children.insert(
1918 "baz".to_owned(),
1919 MemoryNode::File(MemoryFile {
1920 modified: baz_metadata.modified()?,
1921 data: b"abc".to_vec(),
1922 }),
1923 );
1924
1925 children
1926 },
1927 },
1928 },
1929 );
1930 let memory_package = super::Package::from_in_memory(
1931 manifest,
1932 volumes,
1933 atoms,
1934 MemoryVolume {
1935 node: MemoryDir {
1936 modified: SystemTime::UNIX_EPOCH,
1937 nodes: Default::default(),
1938 },
1939 },
1940 Strictness::Lossy,
1941 )?;
1942
1943 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1944
1945 Ok(())
1946 }
1947
1948 #[test]
1949 fn compare_fs_mem_bindings() -> anyhow::Result<()> {
1950 let temp = TempDir::new().unwrap();
1951
1952 let wasmer_toml = r#"
1953 [package]
1954 name = "some/package"
1955 version = "0.0.0"
1956 description = ""
1957 license-file = "LICENSE"
1958 readme = "README.md"
1959
1960 [[module]]
1961 name = "asdf"
1962 source = "asdf.wasm"
1963 abi = "none"
1964 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1965
1966 [fs]
1967 "/dir1" = "local-dir1/dir"
1968 "/dir2" = "local-dir2/dir"
1969 "#;
1970
1971 let manifest = temp.path().join("wasmer.toml");
1972 std::fs::write(&manifest, wasmer_toml).unwrap();
1973
1974 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1975 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1976 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1977 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1978 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1979
1980 let dir1 = temp.path().join("local-dir1").join("dir");
1987 std::fs::create_dir_all(&dir1).unwrap();
1988 let dir2 = temp.path().join("local-dir2").join("dir");
1989 std::fs::create_dir_all(&dir2).unwrap();
1990
1991 let fs_package = super::Package::from_manifest(manifest)?;
1992
1993 let manifest = toml::from_str(wasmer_toml)?;
1994
1995 let mut atoms = BTreeMap::new();
1996 atoms.insert(
1997 "asdf".to_owned(),
1998 (None, OwnedBuffer::from_static(b"\0asm...")),
1999 );
2000
2001 let mut volumes = BTreeMap::new();
2002 volumes.insert(
2003 "/local-dir1/dir".to_owned(),
2004 MemoryVolume {
2005 node: MemoryDir {
2006 modified: SystemTime::UNIX_EPOCH,
2007 nodes: Default::default(),
2008 },
2009 },
2010 );
2011 volumes.insert(
2012 "/local-dir2/dir".to_owned(),
2013 MemoryVolume {
2014 node: MemoryDir {
2015 modified: SystemTime::UNIX_EPOCH,
2016 nodes: Default::default(),
2017 },
2018 },
2019 );
2020
2021 let memory_package = super::Package::from_in_memory(
2022 manifest,
2023 volumes,
2024 atoms,
2025 MemoryVolume {
2026 node: MemoryDir {
2027 modified: SystemTime::UNIX_EPOCH,
2028 nodes: {
2029 let mut children = BTreeMap::new();
2030
2031 children.insert(
2032 "README.md".to_owned(),
2033 MemoryNode::File(MemoryFile {
2034 modified: temp.path().join("README.md").metadata()?.modified()?,
2035 data: b"readme".to_vec(),
2036 }),
2037 );
2038
2039 children.insert(
2040 "LICENSE".to_owned(),
2041 MemoryNode::File(MemoryFile {
2042 modified: temp.path().join("LICENSE").metadata()?.modified()?,
2043 data: b"license".to_vec(),
2044 }),
2045 );
2046
2047 children.insert(
2048 "asdf.wai".to_owned(),
2049 MemoryNode::File(MemoryFile {
2050 modified: temp.path().join("asdf.wai").metadata()?.modified()?,
2051 data: b"exports".to_vec(),
2052 }),
2053 );
2054
2055 children.insert(
2056 "browser.wai".to_owned(),
2057 MemoryNode::File(MemoryFile {
2058 modified: temp.path().join("browser.wai").metadata()?.modified()?,
2059 data: b"imports".to_vec(),
2060 }),
2061 );
2062
2063 children
2064 },
2065 },
2066 },
2067 Strictness::Lossy,
2068 )?;
2069
2070 let memory_package = memory_package.serialize()?;
2071 let fs_package = fs_package.serialize()?;
2072
2073 assert_eq!(memory_package, fs_package);
2074
2075 Ok(())
2076 }
2077}