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 manifest, atoms) =
473 super::manifest::in_memory_wasmer_manifest_to_webc(&manifest, &atoms)?;
474
475 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
476 let mut wapm: Wapm = entry.deserialized()?;
477
478 wapm.name.take();
479 wapm.version.take();
480 wapm.description.take();
481
482 *entry = ciborium::value::Value::serialized(&wapm)?;
483 };
484
485 Ok(Package {
486 base_dir: BaseDir::Path(Path::new("/").to_path_buf()),
487 manifest,
488 atoms,
489 strictness,
490 volumes,
491 })
492 }
493
494 fn load(
495 wasmer_toml: WasmerManifest,
496 base_dir: impl Into<BaseDir>,
497 strictness: Strictness,
498 walker_factory: WalkBuilderFactory,
499 ) -> Result<Self, WasmerPackageError> {
500 let base_dir = base_dir.into();
501
502 if strictness.is_strict() {
503 wasmer_toml.validate()?;
504 }
505
506 let (mut manifest, atoms) =
507 wasmer_manifest_to_webc(&wasmer_toml, base_dir.path(), strictness)?;
508
509 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
511 let mut wapm: Wapm = entry.deserialized()?;
512
513 wapm.name.take();
514 wapm.version.take();
515 wapm.description.take();
516
517 *entry = ciborium::value::Value::serialized(&wapm)?;
518 };
519
520 let base_dir_path = base_dir.path().to_path_buf();
522 let metadata_volume = FsVolume::new_metadata(&wasmer_toml, base_dir_path.clone())?;
524 let mut volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>> = {
526 let old = FsVolume::new_assets(&wasmer_toml, &base_dir_path, walker_factory)?;
527 let mut new = BTreeMap::new();
528
529 for (k, v) in old.into_iter() {
530 new.insert(k, Arc::new(v) as _);
531 }
532
533 new
534 };
535 volumes.insert(
536 metadata_volume.name().to_string(),
537 Arc::new(metadata_volume),
538 );
539
540 Ok(Package {
541 base_dir,
542 manifest,
543 atoms,
544 strictness,
545 volumes,
546 })
547 }
548
549 pub fn webc_hash(&self) -> Option<[u8; 32]> {
551 None
552 }
553
554 pub fn manifest(&self) -> &WebcManifest {
556 &self.manifest
557 }
558
559 pub fn atoms(&self) -> &BTreeMap<String, OwnedBuffer> {
561 &self.atoms
562 }
563
564 pub fn volumes(
566 &self,
567 ) -> impl Iterator<Item = &Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
568 self.volumes.values()
569 }
570
571 pub fn serialize(&self) -> Result<Bytes, Error> {
574 let mut w = Writer::new(ChecksumAlgorithm::Sha256)
575 .write_manifest(self.manifest())?
576 .write_atoms(self.atom_entries()?)?;
577
578 for (name, volume) in &self.volumes {
579 w.write_volume(name.as_str(), volume.as_directory_tree(self.strictness)?)?;
580 }
581
582 let serialized = w.finish(webc::v3::SignatureAlgorithm::None)?;
583
584 Ok(serialized)
585 }
586
587 fn atom_entries(&self) -> Result<BTreeMap<PathSegment, FileEntry<'_>>, Error> {
588 self.atoms()
589 .iter()
590 .map(|(key, value)| {
591 let filename = PathSegment::parse(key)
592 .with_context(|| format!("\"{key}\" isn't a valid atom name"))?;
593 Ok((filename, FileEntry::borrowed(value, Timestamps::default())))
595 })
596 .collect()
597 }
598
599 pub(crate) fn get_volume(
600 &self,
601 name: &str,
602 ) -> Option<Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
603 self.volumes.get(name).cloned()
604 }
605
606 pub(crate) fn volume_names(&self) -> Vec<Cow<'_, str>> {
607 self.volumes
608 .keys()
609 .map(|name| Cow::Borrowed(name.as_str()))
610 .collect()
611 }
612}
613
614impl AbstractWebc for Package {
615 fn version(&self) -> Version {
616 Version::V3
617 }
618
619 fn manifest(&self) -> &WebcManifest {
620 self.manifest()
621 }
622
623 fn atom_names(&self) -> Vec<Cow<'_, str>> {
624 self.atoms()
625 .keys()
626 .map(|s| Cow::Borrowed(s.as_str()))
627 .collect()
628 }
629
630 fn get_atom(&self, name: &str) -> Option<OwnedBuffer> {
631 self.atoms().get(name).cloned()
632 }
633
634 fn get_webc_hash(&self) -> Option<[u8; 32]> {
635 self.webc_hash()
636 }
637
638 fn get_atoms_hash(&self) -> Option<[u8; 32]> {
639 None
640 }
641
642 fn volume_names(&self) -> Vec<Cow<'_, str>> {
643 self.volume_names()
644 }
645
646 fn get_volume(&self, name: &str) -> Option<Volume> {
647 self.get_volume(name).map(|v| {
648 let a: Arc<dyn AbstractVolume + Send + Sync + 'static> = v.as_volume();
649
650 Volume::from(a)
651 })
652 }
653}
654
655impl From<Package> for Container {
656 fn from(value: Package) -> Self {
657 Container::new(value)
658 }
659}
660
661const IS_WASI: bool = cfg!(all(target_family = "wasm", target_os = "wasi"));
662
663fn tempdir() -> Result<TempDir, std::io::Error> {
671 if !IS_WASI {
672 return TempDir::new();
674 }
675
676 let temp_dir: PathBuf = std::env::var("TMPDIR")
679 .unwrap_or_else(|_| "/tmp".to_string())
680 .into();
681
682 if temp_dir.exists() {
683 TempDir::new_in(temp_dir)
684 } else {
685 if let Ok(current_exe) = std::env::current_exe()
691 && let Some(parent) = current_exe.parent()
692 && let Ok(temp) = TempDir::new_in(parent)
693 {
694 return Ok(temp);
695 }
696
697 std::fs::create_dir_all(&temp_dir)?;
699 TempDir::new_in(temp_dir)
700 }
701}
702
703fn unpack_archive(
710 mut archive: Archive<impl std::io::Read>,
711 dest: &Path,
712) -> Result<(), std::io::Error> {
713 cfg_if::cfg_if! {
714 if #[cfg(all(target_family = "wasm", target_os = "wasi"))]
715 {
716 for entry in archive.entries()? {
719 let mut entry = entry?;
720 let item_path = entry.path()?;
721 let full_path = resolve_archive_path(dest, &item_path);
722
723 match entry.header().entry_type() {
724 tar::EntryType::Directory => {
725 std::fs::create_dir_all(&full_path)?;
726 }
727 tar::EntryType::Regular => {
728 if let Some(parent) = full_path.parent() {
729 std::fs::create_dir_all(parent)?;
730 }
731 let mut f = File::create(&full_path)?;
732 std::io::copy(&mut entry, &mut f)?;
733
734 let mtime = entry.header().mtime().unwrap_or_default();
735 if let Err(e) = set_timestamp(full_path.as_path(), mtime) {
736 println!("WARN: {e:?}");
737 }
738 }
739 _ => {}
740 }
741 }
742 Ok(())
743
744 } else {
745 archive.unpack(dest)
746 }
747 }
748}
749
750#[cfg(all(target_family = "wasm", target_os = "wasi"))]
751fn set_timestamp(path: &Path, timestamp: u64) -> Result<(), anyhow::Error> {
752 let fd = unsafe {
753 libc::open(
754 path.as_os_str().as_encoded_bytes().as_ptr() as _,
755 libc::O_RDONLY,
756 )
757 };
758
759 if fd < 0 {
760 anyhow::bail!(format!("failed to open: {}", path.display()));
761 }
762
763 let timespec = [
764 libc::timespec {
766 tv_sec: unsafe { libc::time(std::ptr::null_mut()) }, tv_nsec: 0,
768 },
769 libc::timespec {
771 tv_sec: timestamp as i64,
772 tv_nsec: 0,
773 },
774 ];
775
776 let res = unsafe { libc::futimens(fd, timespec.as_ptr() as _) };
777
778 if res < 0 {
779 anyhow::bail!("failed to set timestamp for: {}", path.display());
780 }
781
782 Ok(())
783}
784
785#[cfg(all(target_family = "wasm", target_os = "wasi"))]
786fn resolve_archive_path(base_dir: &Path, path: &Path) -> PathBuf {
787 let mut buffer = base_dir.to_path_buf();
788
789 for component in path.components() {
790 match component {
791 std::path::Component::Prefix(_)
792 | std::path::Component::RootDir
793 | std::path::Component::CurDir => continue,
794 std::path::Component::ParentDir => {
795 buffer.pop();
796 }
797 std::path::Component::Normal(segment) => {
798 buffer.push(segment);
799 }
800 }
801 }
802
803 buffer
804}
805
806fn read_manifest(base_dir: &Path) -> Result<(PathBuf, WasmerManifest), WasmerPackageError> {
807 for path in ["wasmer.toml", "wapm.toml"] {
808 let path = base_dir.join(path);
809
810 match std::fs::read_to_string(&path) {
811 Ok(s) => {
812 let toml_file = toml::from_str(&s).map_err({
813 let path = path.clone();
814 |error| WasmerPackageError::TomlDeserialize { path, error }
815 })?;
816
817 return Ok((path, toml_file));
818 }
819 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
820 Err(error) => {
821 return Err(WasmerPackageError::FileRead { path, error });
822 }
823 }
824 }
825
826 Err(WasmerPackageError::MissingManifest)
827}
828
829#[derive(Debug)]
830enum BaseDir {
831 Path(PathBuf),
833 Temp(TempDir),
835}
836
837impl BaseDir {
838 fn path(&self) -> &Path {
839 match self {
840 BaseDir::Path(p) => p.as_path(),
841 BaseDir::Temp(t) => t.path(),
842 }
843 }
844}
845
846impl From<TempDir> for BaseDir {
847 fn from(v: TempDir) -> Self {
848 Self::Temp(v)
849 }
850}
851
852impl From<PathBuf> for BaseDir {
853 fn from(v: PathBuf) -> Self {
854 Self::Path(v)
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use std::{
861 collections::BTreeMap,
862 fs::File,
863 path::{Path, PathBuf},
864 str::FromStr,
865 time::SystemTime,
866 };
867
868 use flate2::{Compression, write::GzEncoder};
869 use sha2::Digest;
870 use shared_buffer::OwnedBuffer;
871 use tempfile::TempDir;
872 use webc::{
873 PathSegment, PathSegments,
874 metadata::{
875 Binding, BindingsExtended, WaiBindings, WitBindings,
876 annotations::{FileSystemMapping, VolumeSpecificPath},
877 },
878 };
879
880 use crate::{package::*, utils::from_bytes};
881
882 #[test]
883 fn nonexistent_files() {
884 let temp = TempDir::new().unwrap();
885
886 assert!(Package::from_manifest(temp.path().join("nonexistent.toml")).is_err());
887 assert!(Package::from_tarball_file(temp.path().join("nonexistent.tar.gz")).is_err());
888 }
889
890 #[test]
891 fn load_a_tarball() {
892 let coreutils = Path::new(env!("CARGO_MANIFEST_DIR"))
893 .join("..")
894 .join("..")
895 .join("wasmer-test-files")
896 .join("legacy")
897 .join("coreutils-1.0.11.tar.gz");
898 assert!(coreutils.exists());
899
900 let package = Package::from_tarball_file(coreutils).unwrap();
901
902 let wapm = package.manifest().wapm().unwrap().unwrap();
903 assert!(wapm.name.is_none());
904 assert!(wapm.version.is_none());
905 assert!(wapm.description.is_none());
906 }
907
908 #[test]
909 fn tarball_with_no_manifest() {
910 let temp = TempDir::new().unwrap();
911 let empty_tarball = temp.path().join("empty.tar.gz");
912 let mut f = File::create(&empty_tarball).unwrap();
913 tar::Builder::new(GzEncoder::new(&mut f, Compression::fast()))
914 .finish()
915 .unwrap();
916
917 assert!(Package::from_tarball_file(&empty_tarball).is_err());
918 }
919
920 #[test]
921 fn empty_package_on_disk() {
922 let temp = TempDir::new().unwrap();
923 let manifest = temp.path().join("wasmer.toml");
924 std::fs::write(
925 &manifest,
926 r#"
927 [package]
928 name = "some/package"
929 version = "0.0.0"
930 description = "A dummy package"
931 "#,
932 )
933 .unwrap();
934
935 let package = Package::from_manifest(&manifest).unwrap();
936
937 let wapm = package.manifest().wapm().unwrap().unwrap();
938 assert!(wapm.name.is_none());
939 assert!(wapm.version.is_none());
940 assert!(wapm.description.is_none());
941 }
942
943 #[test]
944 fn load_old_cowsay() {
945 let tarball = Path::new(env!("CARGO_MANIFEST_DIR"))
946 .join("..")
947 .join("..")
948 .join("wasmer-test-files")
949 .join("legacy")
950 .join("cowsay-0.3.0.tar.gz");
951
952 let pkg = Package::from_tarball_file(tarball).unwrap();
953
954 insta::assert_yaml_snapshot!(pkg.manifest());
955 assert_eq!(
956 pkg.manifest.commands.keys().collect::<Vec<_>>(),
957 ["cowsay", "cowthink"],
958 );
959 }
960
961 #[test]
962 fn serialize_package_with_non_existent_fs() {
963 let temp = TempDir::new().unwrap();
964 let wasmer_toml = r#"
965 [package]
966 name = "some/package"
967 version = "0.0.0"
968 description = "Test package"
969
970 [fs]
971 "/first" = "./first"
972 "#;
973 let manifest = temp.path().join("wasmer.toml");
974
975 std::fs::write(&manifest, wasmer_toml).unwrap();
976
977 let error = Package::from_manifest(manifest).unwrap_err();
978
979 match error {
980 WasmerPackageError::PathNotExists { path } => {
981 assert_eq!(path, PathBuf::from_str("./first").unwrap());
982 }
983 e => panic!("unexpected error: {e:?}"),
984 }
985 }
986
987 #[test]
988 fn serialize_package_with_bundled_directories() {
989 let temp = TempDir::new().unwrap();
990 let wasmer_toml = r#"
991 [package]
992 name = "some/package"
993 version = "0.0.0"
994 description = "Test package"
995
996 [fs]
997 "/first" = "first"
998 second = "nested/dir"
999 "second/child" = "third"
1000 empty = "empty"
1001 "#;
1002 let manifest = temp.path().join("wasmer.toml");
1003 std::fs::write(&manifest, wasmer_toml).unwrap();
1004 let first = temp.path().join("first");
1022 std::fs::create_dir_all(&first).unwrap();
1023 std::fs::write(first.join("file.txt"), "File").unwrap();
1024 let second = temp.path().join("nested").join("dir");
1026 std::fs::create_dir_all(&second).unwrap();
1027 std::fs::write(second.join(".wasmerignore"), "ignore_me").unwrap();
1028 std::fs::write(second.join(".hidden"), "something something").unwrap();
1029 std::fs::write(second.join("ignore_me"), "something something").unwrap();
1030 std::fs::write(second.join("README.md"), "please").unwrap();
1031 let another_dir = temp.path().join("nested").join("dir").join("another-dir");
1032 std::fs::create_dir_all(&another_dir).unwrap();
1033 std::fs::write(another_dir.join("empty.txt"), "").unwrap();
1034 let third = temp.path().join("third");
1036 std::fs::create_dir_all(&third).unwrap();
1037 std::fs::write(third.join("file.txt"), "Hello, World!").unwrap();
1038 let empty_dir = temp.path().join("empty");
1040 std::fs::create_dir_all(empty_dir).unwrap();
1041
1042 let package = Package::from_manifest(manifest).unwrap();
1043
1044 let webc = package.serialize().unwrap();
1045 let webc = from_bytes(webc).unwrap();
1046 let manifest = webc.manifest();
1047 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1048 assert!(wapm_metadata.name.is_none());
1049 assert!(wapm_metadata.version.is_none());
1050 assert!(wapm_metadata.description.is_none());
1051 let fs_table = manifest.filesystem().unwrap().unwrap();
1052 assert_eq!(
1053 fs_table,
1054 [
1055 FileSystemMapping {
1056 from: None,
1057 volume_name: "/first".to_string(),
1058 host_path: None,
1059 mount_path: "/first".to_string(),
1060 },
1061 FileSystemMapping {
1062 from: None,
1063 volume_name: "/nested/dir".to_string(),
1064 host_path: None,
1065 mount_path: "/second".to_string(),
1066 },
1067 FileSystemMapping {
1068 from: None,
1069 volume_name: "/third".to_string(),
1070 host_path: None,
1071 mount_path: "/second/child".to_string(),
1072 },
1073 FileSystemMapping {
1074 from: None,
1075 volume_name: "/empty".to_string(),
1076 host_path: None,
1077 mount_path: "/empty".to_string(),
1078 },
1079 ]
1080 );
1081
1082 let first_file_hash: [u8; 32] = sha2::Sha256::digest(b"File").into();
1083 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"please").into();
1084 let empty_hash: [u8; 32] = sha2::Sha256::digest(b"").into();
1085 let third_file_hash: [u8; 32] = sha2::Sha256::digest(b"Hello, World!").into();
1086
1087 let first_volume = webc.get_volume("/first").unwrap();
1088 assert_eq!(
1089 first_volume.read_file("/file.txt").unwrap(),
1090 (b"File".as_slice().into(), Some(first_file_hash)),
1091 );
1092
1093 let nested_dir_volume = webc.get_volume("/nested/dir").unwrap();
1094 assert_eq!(
1095 nested_dir_volume.read_file("README.md").unwrap(),
1096 (b"please".as_slice().into(), Some(readme_hash)),
1097 );
1098 assert!(nested_dir_volume.read_file(".wasmerignore").is_some());
1099 assert!(nested_dir_volume.read_file(".hidden").is_some());
1100 assert!(nested_dir_volume.read_file("ignore_me").is_none());
1101 assert_eq!(
1102 nested_dir_volume
1103 .read_file("/another-dir/empty.txt")
1104 .unwrap(),
1105 (b"".as_slice().into(), Some(empty_hash))
1106 );
1107
1108 let third_volume = webc.get_volume("/third").unwrap();
1109 assert_eq!(
1110 third_volume.read_file("/file.txt").unwrap(),
1111 (b"Hello, World!".as_slice().into(), Some(third_file_hash))
1112 );
1113
1114 let empty_volume = webc.get_volume("/empty").unwrap();
1115 assert_eq!(
1116 empty_volume.read_dir("/").unwrap().len(),
1117 0,
1118 "Directories should be included, even if empty"
1119 );
1120 }
1121
1122 #[cfg(unix)]
1123 #[test]
1124 fn serialize_package_with_symlinks() {
1125 let temp = TempDir::new().unwrap();
1126 let wasmer_toml = r#"
1127 [package]
1128 name = "some/package"
1129 version = "0.0.0"
1130 description = "Test package"
1131
1132 [fs]
1133 "/assets" = "assets"
1134 "#;
1135 let manifest = temp.path().join("wasmer.toml");
1136 std::fs::write(&manifest, wasmer_toml).unwrap();
1137
1138 let assets = temp.path().join("assets");
1139 std::fs::create_dir(&assets).unwrap();
1140 let target = assets.join("target.txt");
1141 let target_dir = assets.join("target-dir");
1142
1143 std::fs::write(&target, "target").unwrap();
1144 std::fs::create_dir(&target_dir).unwrap();
1145 std::os::unix::fs::symlink("target.txt", assets.join("file-link")).unwrap();
1146 std::os::unix::fs::symlink("target-dir", assets.join("dir-link")).unwrap();
1147 std::os::unix::fs::symlink("subdir/../target.txt", assets.join("nested-link")).unwrap();
1148 std::os::unix::fs::symlink("missing.txt", assets.join("broken-link")).unwrap();
1149
1150 let package = Package::from_manifest(manifest).unwrap();
1151 let webc = from_bytes(package.serialize().unwrap()).unwrap();
1152 let volume = webc.get_volume("/assets").unwrap();
1153
1154 assert!(volume.read_file("/file-link").is_none());
1155 assert_eq!(
1156 volume.read_link("/file-link").unwrap().0,
1157 "target.txt".to_string()
1158 );
1159 assert_eq!(
1160 volume.read_link("/dir-link").unwrap().0,
1161 "target-dir".to_string()
1162 );
1163 assert_eq!(
1164 volume.read_link("/nested-link").unwrap().0,
1165 "subdir/../target.txt".to_string()
1166 );
1167 assert_eq!(
1168 volume.read_link("/broken-link").unwrap().0,
1169 "missing.txt".to_string()
1170 );
1171 assert!(volume.metadata("/broken-link").unwrap().is_symlink());
1172 assert!(
1173 volume
1174 .read_dir("/")
1175 .unwrap()
1176 .iter()
1177 .any(|(name, _, meta)| name.as_str() == "file-link" && meta.is_symlink())
1178 );
1179 }
1180
1181 #[test]
1182 fn serialize_package_with_metadata_files() {
1183 let temp = TempDir::new().unwrap();
1184 let wasmer_toml = r#"
1185 [package]
1186 name = "some/package"
1187 version = "0.0.0"
1188 description = "Test package"
1189 readme = "README.md"
1190 license-file = "LICENSE"
1191 "#;
1192 let manifest = temp.path().join("wasmer.toml");
1193 std::fs::write(&manifest, wasmer_toml).unwrap();
1194 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1195 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1196
1197 let serialized = Package::from_manifest(manifest)
1198 .unwrap()
1199 .serialize()
1200 .unwrap();
1201
1202 let webc = from_bytes(serialized).unwrap();
1203 let metadata_volume = webc.get_volume("metadata").unwrap();
1204
1205 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1206 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1207
1208 assert_eq!(
1209 metadata_volume.read_file("/README.md").unwrap(),
1210 (b"readme".as_slice().into(), Some(readme_hash))
1211 );
1212 assert_eq!(
1213 metadata_volume.read_file("/LICENSE").unwrap(),
1214 (b"license".as_slice().into(), Some(license_hash))
1215 );
1216 }
1217
1218 #[test]
1219 fn load_package_with_wit_bindings() {
1220 let temp = TempDir::new().unwrap();
1221 let wasmer_toml = r#"
1222 [package]
1223 name = "some/package"
1224 version = "0.0.0"
1225 description = ""
1226
1227 [[module]]
1228 name = "my-lib"
1229 source = "./my-lib.wasm"
1230 abi = "none"
1231 bindings = { wit-bindgen = "0.1.0", wit-exports = "./file.wit" }
1232 "#;
1233 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1234 std::fs::write(temp.path().join("file.wit"), "file").unwrap();
1235 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1236
1237 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1238 .unwrap()
1239 .serialize()
1240 .unwrap();
1241 let webc = from_bytes(package).unwrap();
1242
1243 assert_eq!(
1244 webc.manifest().bindings,
1245 vec![Binding {
1246 name: "library-bindings".to_string(),
1247 kind: "wit@0.1.0".to_string(),
1248 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wit(
1249 WitBindings {
1250 exports: "metadata://file.wit".to_string(),
1251 module: "my-lib".to_string(),
1252 }
1253 ))
1254 .unwrap(),
1255 }]
1256 );
1257 let metadata_volume = webc.get_volume("metadata").unwrap();
1258 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1259 assert_eq!(
1260 metadata_volume.read_file("/file.wit").unwrap(),
1261 (b"file".as_slice().into(), Some(file_hash))
1262 );
1263 insta::with_settings! {
1264 { description => wasmer_toml },
1265 { insta::assert_yaml_snapshot!(webc.manifest()); }
1266 }
1267 }
1268
1269 #[test]
1270 fn load_package_with_wai_bindings() {
1271 let temp = TempDir::new().unwrap();
1272 let wasmer_toml = r#"
1273 [package]
1274 name = "some/package"
1275 version = "0.0.0"
1276 description = ""
1277
1278 [[module]]
1279 name = "my-lib"
1280 source = "./my-lib.wasm"
1281 abi = "none"
1282 bindings = { wai-version = "0.2.0", exports = "./file.wai", imports = ["a.wai", "b.wai"] }
1283 "#;
1284 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1285 std::fs::write(temp.path().join("file.wai"), "file").unwrap();
1286 std::fs::write(temp.path().join("a.wai"), "a").unwrap();
1287 std::fs::write(temp.path().join("b.wai"), "b").unwrap();
1288 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1289
1290 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1291 .unwrap()
1292 .serialize()
1293 .unwrap();
1294 let webc = from_bytes(package).unwrap();
1295
1296 assert_eq!(
1297 webc.manifest().bindings,
1298 vec![Binding {
1299 name: "library-bindings".to_string(),
1300 kind: "wai@0.2.0".to_string(),
1301 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wai(
1302 WaiBindings {
1303 exports: Some("metadata://file.wai".to_string()),
1304 module: "my-lib".to_string(),
1305 imports: vec![
1306 "metadata://a.wai".to_string(),
1307 "metadata://b.wai".to_string(),
1308 ]
1309 }
1310 ))
1311 .unwrap(),
1312 }]
1313 );
1314 let metadata_volume = webc.get_volume("metadata").unwrap();
1315
1316 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1317 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1318 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1319
1320 assert_eq!(
1321 metadata_volume.read_file("/file.wai").unwrap(),
1322 (b"file".as_slice().into(), Some(file_hash))
1323 );
1324 assert_eq!(
1325 metadata_volume.read_file("/a.wai").unwrap(),
1326 (b"a".as_slice().into(), Some(a_hash))
1327 );
1328 assert_eq!(
1329 metadata_volume.read_file("/b.wai").unwrap(),
1330 (b"b".as_slice().into(), Some(b_hash))
1331 );
1332 insta::with_settings! {
1333 { description => wasmer_toml },
1334 { insta::assert_yaml_snapshot!(webc.manifest()); }
1335 }
1336 }
1337
1338 #[test]
1340 fn absolute_paths_in_wasmer_toml_issue_105() {
1341 let temp = TempDir::new().unwrap();
1342 let base_dir = temp.path().canonicalize().unwrap();
1343 let sep = std::path::MAIN_SEPARATOR;
1344 let wasmer_toml = format!(
1345 r#"
1346 [package]
1347 name = 'some/package'
1348 version = '0.0.0'
1349 description = 'Test package'
1350 readme = '{BASE_DIR}{sep}README.md'
1351 license-file = '{BASE_DIR}{sep}LICENSE'
1352
1353 [[module]]
1354 name = 'first'
1355 source = '{BASE_DIR}{sep}target{sep}debug{sep}package.wasm'
1356 bindings = {{ wai-version = '0.2.0', exports = '{BASE_DIR}{sep}bindings{sep}file.wai', imports = ['{BASE_DIR}{sep}bindings{sep}a.wai'] }}
1357 "#,
1358 BASE_DIR = base_dir.display(),
1359 );
1360 let manifest = temp.path().join("wasmer.toml");
1361 std::fs::write(&manifest, &wasmer_toml).unwrap();
1362 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1363 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1364 let bindings = temp.path().join("bindings");
1365 std::fs::create_dir_all(&bindings).unwrap();
1366 std::fs::write(bindings.join("file.wai"), "file.wai").unwrap();
1367 std::fs::write(bindings.join("a.wai"), "a.wai").unwrap();
1368 let target = temp.path().join("target").join("debug");
1369 std::fs::create_dir_all(&target).unwrap();
1370 std::fs::write(target.join("package.wasm"), b"\0asm...").unwrap();
1371
1372 let serialized = Package::from_manifest(manifest)
1373 .unwrap()
1374 .serialize()
1375 .unwrap();
1376
1377 let webc = from_bytes(serialized).unwrap();
1378 let manifest = webc.manifest();
1379 let wapm = manifest.wapm().unwrap().unwrap();
1380
1381 let lookup = |item: VolumeSpecificPath| {
1383 let volume = webc.get_volume(&item.volume).unwrap();
1384 let (contents, _) = volume.read_file(&item.path).unwrap();
1385 String::from_utf8(contents.into()).unwrap()
1386 };
1387 assert_eq!(lookup(wapm.license_file.unwrap()), "license");
1388 assert_eq!(lookup(wapm.readme.unwrap()), "readme");
1389
1390 let lookup = |item: &str| {
1393 let (volume, path) = item.split_once(":/").unwrap();
1394 let volume = webc.get_volume(volume).unwrap();
1395 let (content, _) = volume.read_file(path).unwrap();
1396 String::from_utf8(content.into()).unwrap()
1397 };
1398 let bindings = manifest.bindings[0].get_wai_bindings().unwrap();
1399 assert_eq!(lookup(&bindings.imports[0]), "a.wai");
1400 assert_eq!(lookup(bindings.exports.unwrap().as_str()), "file.wai");
1401
1402 let mut settings = insta::Settings::clone_current();
1404 let base_dir = base_dir.display().to_string();
1405 settings.set_description(wasmer_toml.replace(&base_dir, "[BASE_DIR]"));
1406 let filter = regex::escape(&base_dir);
1407 settings.add_filter(&filter, "[BASE_DIR]");
1408 settings.bind(|| {
1409 insta::assert_yaml_snapshot!(webc.manifest());
1410 });
1411 }
1412
1413 #[test]
1414 fn serializing_will_skip_missing_metadata_by_default() {
1415 let temp = TempDir::new().unwrap();
1416 let wasmer_toml = r#"
1417 [package]
1418 name = 'some/package'
1419 version = '0.0.0'
1420 description = 'Test package'
1421 readme = '/this/does/not/exist/README.md'
1422 license-file = 'LICENSE.wtf'
1423 "#;
1424 let manifest = temp.path().join("wasmer.toml");
1425 std::fs::write(&manifest, wasmer_toml).unwrap();
1426 let pkg = Package::from_manifest(manifest).unwrap();
1427
1428 let serialized = pkg.serialize().unwrap();
1429
1430 let webc = from_bytes(serialized).unwrap();
1431 let manifest = webc.manifest();
1432 let wapm = manifest.wapm().unwrap().unwrap();
1433 assert!(wapm.license_file.is_none());
1435 assert!(wapm.readme.is_none());
1436
1437 let pkg = Package {
1439 strictness: Strictness::Strict,
1440 ..pkg
1441 };
1442 assert!(pkg.serialize().is_err());
1443 }
1444
1445 #[test]
1446 fn serialize_package_without_local_base_fs_paths() {
1447 let temp = TempDir::new().unwrap();
1448 let wasmer_toml = r#"
1449 [package]
1450 name = "some/package"
1451 version = "0.0.0"
1452 description = "Test package"
1453 readme = 'README.md'
1454 license-file = 'LICENSE'
1455
1456 [fs]
1457 "/path_in_wasix" = "local-dir/dir1"
1458 "#;
1459 let manifest = temp.path().join("wasmer.toml");
1460 std::fs::write(&manifest, wasmer_toml).unwrap();
1461
1462 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1463 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1464
1465 let dir1 = temp.path().join("local-dir").join("dir1");
1472 std::fs::create_dir_all(&dir1).unwrap();
1473
1474 let a = dir1.join("a");
1475 let b = dir1.join("b");
1476
1477 std::fs::write(a, "a").unwrap();
1478 std::fs::write(b, "b").unwrap();
1479
1480 let package = Package::from_manifest(manifest).unwrap();
1481
1482 let webc = package.serialize().unwrap();
1483 let webc = from_bytes(webc).unwrap();
1484 let manifest = webc.manifest();
1485 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1486
1487 assert!(wapm_metadata.name.is_none());
1488 assert!(wapm_metadata.version.is_none());
1489 assert!(wapm_metadata.description.is_none());
1490
1491 let fs_table = manifest.filesystem().unwrap().unwrap();
1492 assert_eq!(
1493 fs_table,
1494 [FileSystemMapping {
1495 from: None,
1496 volume_name: "/local-dir/dir1".to_string(),
1497 host_path: None,
1498 mount_path: "/path_in_wasix".to_string(),
1499 },]
1500 );
1501
1502 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1503 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1504
1505 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1506 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1507
1508 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1509 let meta_volume = webc.get_volume("metadata").unwrap();
1510
1511 assert_eq!(
1512 meta_volume.read_file("LICENSE").unwrap(),
1513 (b"license".as_slice().into(), Some(license_hash)),
1514 );
1515 assert_eq!(
1516 meta_volume.read_file("README.md").unwrap(),
1517 (b"readme".as_slice().into(), Some(readme_hash)),
1518 );
1519 assert_eq!(
1520 dir1_volume.read_file("a").unwrap(),
1521 (b"a".as_slice().into(), Some(a_hash))
1522 );
1523 assert_eq!(
1524 dir1_volume.read_file("b").unwrap(),
1525 (b"b".as_slice().into(), Some(b_hash))
1526 );
1527 }
1528
1529 #[test]
1530 fn serialize_package_with_nested_fs_entries_without_local_base_fs_paths() {
1531 let temp = TempDir::new().unwrap();
1532 let wasmer_toml = r#"
1533 [package]
1534 name = "some/package"
1535 version = "0.0.0"
1536 description = "Test package"
1537 readme = 'README.md'
1538 license-file = 'LICENSE'
1539
1540 [fs]
1541 "/path_in_wasix" = "local-dir/dir1"
1542 "#;
1543 let manifest = temp.path().join("wasmer.toml");
1544 std::fs::write(&manifest, wasmer_toml).unwrap();
1545
1546 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1547 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1548
1549 let local_dir = temp.path().join("local-dir");
1557 std::fs::create_dir_all(&local_dir).unwrap();
1558
1559 let dir1 = local_dir.join("dir1");
1560 std::fs::create_dir_all(&dir1).unwrap();
1561
1562 let dir2 = dir1.join("dir2");
1563 std::fs::create_dir_all(&dir2).unwrap();
1564
1565 let a = dir2.join("a");
1566 let b = dir1.join("b");
1567
1568 std::fs::write(a, "a").unwrap();
1569 std::fs::write(b, "b").unwrap();
1570
1571 let package = Package::from_manifest(manifest).unwrap();
1572
1573 let webc = package.serialize().unwrap();
1574 let webc = from_bytes(webc).unwrap();
1575 let manifest = webc.manifest();
1576 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1577
1578 assert!(wapm_metadata.name.is_none());
1579 assert!(wapm_metadata.version.is_none());
1580 assert!(wapm_metadata.description.is_none());
1581
1582 let fs_table = manifest.filesystem().unwrap().unwrap();
1583 assert_eq!(
1584 fs_table,
1585 [FileSystemMapping {
1586 from: None,
1587 volume_name: "/local-dir/dir1".to_string(),
1588 host_path: None,
1589 mount_path: "/path_in_wasix".to_string(),
1590 },]
1591 );
1592
1593 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1594 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1595
1596 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1597 let dir2_hash: [u8; 32] = sha2::Sha256::digest(a_hash).into();
1598 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1599
1600 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1601 let meta_volume = webc.get_volume("metadata").unwrap();
1602
1603 assert_eq!(
1604 meta_volume.read_file("LICENSE").unwrap(),
1605 (b"license".as_slice().into(), Some(license_hash)),
1606 );
1607 assert_eq!(
1608 meta_volume.read_file("README.md").unwrap(),
1609 (b"readme".as_slice().into(), Some(readme_hash)),
1610 );
1611 assert_eq!(
1612 dir1_volume
1613 .read_dir("/")
1614 .unwrap()
1615 .into_iter()
1616 .map(|(p, h, _)| (p, h))
1617 .collect::<Vec<_>>(),
1618 vec![
1619 (PathSegment::parse("b").unwrap(), Some(b_hash)),
1620 (PathSegment::parse("dir2").unwrap(), Some(dir2_hash))
1621 ]
1622 );
1623 assert_eq!(
1624 dir1_volume
1625 .read_dir("/dir2")
1626 .unwrap()
1627 .into_iter()
1628 .map(|(p, h, _)| (p, h))
1629 .collect::<Vec<_>>(),
1630 vec![(PathSegment::parse("a").unwrap(), Some(a_hash))]
1631 );
1632 assert_eq!(
1633 dir1_volume.read_file("/dir2/a").unwrap(),
1634 (b"a".as_slice().into(), Some(a_hash))
1635 );
1636 assert_eq!(
1637 dir1_volume.read_file("/b").unwrap(),
1638 (b"b".as_slice().into(), Some(b_hash))
1639 );
1640 }
1641
1642 #[test]
1643 fn serialize_package_mapped_to_same_dir_without_local_base_fs_paths() {
1644 let temp = TempDir::new().unwrap();
1645 let wasmer_toml = r#"
1646 [package]
1647 name = "some/package"
1648 version = "0.0.0"
1649 description = "Test package"
1650 readme = 'README.md'
1651 license-file = 'LICENSE'
1652
1653 [fs]
1654 "/dir1" = "local-dir1/dir"
1655 "/dir2" = "local-dir2/dir"
1656 "#;
1657 let manifest = temp.path().join("wasmer.toml");
1658 std::fs::write(&manifest, wasmer_toml).unwrap();
1659
1660 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1661 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1662
1663 let dir1 = temp.path().join("local-dir1").join("dir");
1670 std::fs::create_dir_all(&dir1).unwrap();
1671 let dir2 = temp.path().join("local-dir2").join("dir");
1672 std::fs::create_dir_all(&dir2).unwrap();
1673
1674 let package = Package::from_manifest(manifest).unwrap();
1675
1676 let webc = package.serialize().unwrap();
1677 let webc = from_bytes(webc).unwrap();
1678 let manifest = webc.manifest();
1679 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1680
1681 assert!(wapm_metadata.name.is_none());
1682 assert!(wapm_metadata.version.is_none());
1683 assert!(wapm_metadata.description.is_none());
1684
1685 let fs_table = manifest.filesystem().unwrap().unwrap();
1686 assert_eq!(
1687 fs_table,
1688 [
1689 FileSystemMapping {
1690 from: None,
1691 volume_name: "/local-dir1/dir".to_string(),
1692 host_path: None,
1693 mount_path: "/dir1".to_string(),
1694 },
1695 FileSystemMapping {
1696 from: None,
1697 volume_name: "/local-dir2/dir".to_string(),
1698 host_path: None,
1699 mount_path: "/dir2".to_string(),
1700 },
1701 ]
1702 );
1703
1704 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1705 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1706
1707 let dir1_volume = webc.get_volume("/local-dir1/dir").unwrap();
1708 let dir2_volume = webc.get_volume("/local-dir2/dir").unwrap();
1709 let meta_volume = webc.get_volume("metadata").unwrap();
1710
1711 assert_eq!(
1712 meta_volume.read_file("LICENSE").unwrap(),
1713 (b"license".as_slice().into(), Some(license_hash)),
1714 );
1715 assert_eq!(
1716 meta_volume.read_file("README.md").unwrap(),
1717 (b"readme".as_slice().into(), Some(readme_hash)),
1718 );
1719 assert!(dir1_volume.read_dir("/").unwrap().is_empty());
1720 assert!(dir2_volume.read_dir("/").unwrap().is_empty());
1721 }
1722
1723 #[test]
1724 fn metadata_only_contains_relevant_files() {
1725 let temp = TempDir::new().unwrap();
1726 let wasmer_toml = r#"
1727 [package]
1728 name = "some/package"
1729 version = "0.0.0"
1730 description = ""
1731 license-file = "./path/to/LICENSE"
1732 readme = "README.md"
1733
1734 [[module]]
1735 name = "asdf"
1736 source = "asdf.wasm"
1737 abi = "none"
1738 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1739 "#;
1740
1741 let manifest = temp.path().join("wasmer.toml");
1742 std::fs::write(&manifest, wasmer_toml).unwrap();
1743
1744 let license_dir = temp.path().join("path").join("to");
1745 std::fs::create_dir_all(&license_dir).unwrap();
1746 std::fs::write(license_dir.join("LICENSE"), "license").unwrap();
1747 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1748 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1749 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1750 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1751 std::fs::write(temp.path().join("unwanted_file.txt"), "unwanted_file").unwrap();
1752
1753 let package = Package::from_manifest(manifest).unwrap();
1754
1755 let contents: Vec<_> = package
1756 .get_volume("metadata")
1757 .unwrap()
1758 .read_dir(&PathSegments::ROOT)
1759 .unwrap()
1760 .into_iter()
1761 .map(|(path, _, _)| path)
1762 .collect();
1763
1764 assert_eq!(
1765 contents,
1766 vec![
1767 PathSegment::parse("README.md").unwrap(),
1768 PathSegment::parse("asdf.wai").unwrap(),
1769 PathSegment::parse("browser.wai").unwrap(),
1770 PathSegment::parse("path").unwrap(),
1771 ]
1772 );
1773 }
1774
1775 #[test]
1776 fn create_from_in_memory() -> anyhow::Result<()> {
1777 let wasmer_toml = r#"
1778 [dependencies]
1779 "wasmer/python" = "3.12.9+build.9"
1780
1781
1782 [[command]]
1783 module = "wasmer/python:python"
1784 name = "hello"
1785 runner = "wasi"
1786
1787 [command.annotations.wasi]
1788 main-args = [ "-c", "import os; print([f for f in os.walk('/public')]); " ]
1789
1790 [fs]
1791 "/public" = "public"
1792 "#;
1793
1794 let manifest = toml::from_str(wasmer_toml)?;
1795
1796 let file_modified = SystemTime::now();
1797 let file_data = String::from("Hello, world!").as_bytes().to_vec();
1798
1799 let file = MemoryFile {
1800 modified: file_modified,
1801 data: file_data,
1802 };
1803
1804 let mut nodes = BTreeMap::new();
1805 nodes.insert(String::from("hello.txt"), MemoryNode::File(file));
1806
1807 let dir_modified = SystemTime::now();
1808 let dir = MemoryDir {
1809 modified: dir_modified,
1810 nodes,
1811 };
1812
1813 let volume = MemoryVolume { node: dir };
1814 let mut volumes = BTreeMap::new();
1815
1816 volumes.insert("public".to_string(), volume);
1817
1818 let atoms = BTreeMap::new();
1819 let package = super::Package::from_in_memory(
1820 manifest,
1821 volumes,
1822 atoms,
1823 MemoryVolume {
1824 node: MemoryDir {
1825 modified: SystemTime::now(),
1826 nodes: BTreeMap::new(),
1827 },
1828 },
1829 Strictness::Strict,
1830 )?;
1831
1832 _ = package.serialize()?;
1833
1834 Ok(())
1835 }
1836
1837 #[test]
1838 fn compare_fs_mem_manifest() -> anyhow::Result<()> {
1839 let wasmer_toml = r#"
1840 [package]
1841 name = "test"
1842 version = "0.0.0"
1843 description = "asdf"
1844 "#;
1845
1846 let temp = TempDir::new()?;
1847 let manifest_path = temp.path().join("wasmer.toml");
1848 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1849
1850 let fs_package = super::Package::from_manifest(manifest_path)?;
1851
1852 let manifest = toml::from_str(wasmer_toml)?;
1853 let memory_package = super::Package::from_in_memory(
1854 manifest,
1855 Default::default(),
1856 Default::default(),
1857 MemoryVolume {
1858 node: MemoryDir {
1859 modified: SystemTime::UNIX_EPOCH,
1860 nodes: BTreeMap::new(),
1861 },
1862 },
1863 Strictness::Lossy,
1864 )?;
1865
1866 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1867
1868 Ok(())
1869 }
1870
1871 #[test]
1872 fn compare_fs_mem_manifest_and_atoms() -> anyhow::Result<()> {
1873 let wasmer_toml = r#"
1874 [package]
1875 name = "test"
1876 version = "0.0.0"
1877 description = "asdf"
1878
1879 [[module]]
1880 name = "foo"
1881 source = "foo.wasm"
1882 abi = "wasi"
1883 "#;
1884
1885 let temp = TempDir::new()?;
1886 let manifest_path = temp.path().join("wasmer.toml");
1887 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1888
1889 let atom_path = temp.path().join("foo.wasm");
1890 std::fs::write(&atom_path, b"").unwrap();
1891
1892 let fs_package = super::Package::from_manifest(manifest_path)?;
1893
1894 let manifest = toml::from_str(wasmer_toml)?;
1895 let mut atoms = BTreeMap::new();
1896 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1897 let memory_package = super::Package::from_in_memory(
1898 manifest,
1899 Default::default(),
1900 atoms,
1901 MemoryVolume {
1902 node: MemoryDir {
1903 modified: SystemTime::UNIX_EPOCH,
1904 nodes: BTreeMap::new(),
1905 },
1906 },
1907 Strictness::Lossy,
1908 )?;
1909
1910 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1911
1912 Ok(())
1913 }
1914
1915 #[test]
1916 fn compare_fs_mem_volume() -> anyhow::Result<()> {
1917 let wasmer_toml = r#"
1918 [package]
1919 name = "test"
1920 version = "0.0.0"
1921 description = "asdf"
1922
1923 [[module]]
1924 name = "foo"
1925 source = "foo.wasm"
1926 abi = "wasi"
1927
1928 [fs]
1929 "/bar" = "bar"
1930 "#;
1931
1932 let temp = TempDir::new()?;
1933 let manifest_path = temp.path().join("wasmer.toml");
1934 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1935
1936 let atom_path = temp.path().join("foo.wasm");
1937 std::fs::write(&atom_path, b"").unwrap();
1938
1939 let bar = temp.path().join("bar");
1940 std::fs::create_dir(&bar).unwrap();
1941
1942 let baz = bar.join("baz");
1943 std::fs::write(&baz, b"abc")?;
1944
1945 let baz_metadata = std::fs::metadata(&baz)?;
1946
1947 let fs_package = super::Package::from_manifest(manifest_path)?;
1948
1949 let manifest = toml::from_str(wasmer_toml)?;
1950
1951 let mut atoms = BTreeMap::new();
1952 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1953
1954 let mut volumes = BTreeMap::new();
1955 volumes.insert(
1956 "/bar".to_owned(),
1957 MemoryVolume {
1958 node: MemoryDir {
1959 modified: SystemTime::UNIX_EPOCH,
1960 nodes: {
1961 let mut children = BTreeMap::new();
1962
1963 children.insert(
1964 "baz".to_owned(),
1965 MemoryNode::File(MemoryFile {
1966 modified: baz_metadata.modified()?,
1967 data: b"abc".to_vec(),
1968 }),
1969 );
1970
1971 children
1972 },
1973 },
1974 },
1975 );
1976 let memory_package = super::Package::from_in_memory(
1977 manifest,
1978 volumes,
1979 atoms,
1980 MemoryVolume {
1981 node: MemoryDir {
1982 modified: SystemTime::UNIX_EPOCH,
1983 nodes: Default::default(),
1984 },
1985 },
1986 Strictness::Lossy,
1987 )?;
1988
1989 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1990
1991 Ok(())
1992 }
1993
1994 #[test]
1995 fn compare_fs_mem_bindings() -> anyhow::Result<()> {
1996 let temp = TempDir::new().unwrap();
1997
1998 let wasmer_toml = r#"
1999 [package]
2000 name = "some/package"
2001 version = "0.0.0"
2002 description = ""
2003 license-file = "LICENSE"
2004 readme = "README.md"
2005
2006 [[module]]
2007 name = "asdf"
2008 source = "asdf.wasm"
2009 abi = "none"
2010 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
2011
2012 [fs]
2013 "/dir1" = "local-dir1/dir"
2014 "/dir2" = "local-dir2/dir"
2015 "#;
2016
2017 let manifest = temp.path().join("wasmer.toml");
2018 std::fs::write(&manifest, wasmer_toml).unwrap();
2019
2020 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
2021 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
2022 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
2023 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
2024 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
2025
2026 let dir1 = temp.path().join("local-dir1").join("dir");
2033 std::fs::create_dir_all(&dir1).unwrap();
2034 let dir2 = temp.path().join("local-dir2").join("dir");
2035 std::fs::create_dir_all(&dir2).unwrap();
2036
2037 let fs_package = super::Package::from_manifest(manifest)?;
2038
2039 let manifest = toml::from_str(wasmer_toml)?;
2040
2041 let mut atoms = BTreeMap::new();
2042 atoms.insert(
2043 "asdf".to_owned(),
2044 (None, OwnedBuffer::from_static(b"\0asm...")),
2045 );
2046
2047 let mut volumes = BTreeMap::new();
2048 volumes.insert(
2049 "/local-dir1/dir".to_owned(),
2050 MemoryVolume {
2051 node: MemoryDir {
2052 modified: SystemTime::UNIX_EPOCH,
2053 nodes: Default::default(),
2054 },
2055 },
2056 );
2057 volumes.insert(
2058 "/local-dir2/dir".to_owned(),
2059 MemoryVolume {
2060 node: MemoryDir {
2061 modified: SystemTime::UNIX_EPOCH,
2062 nodes: Default::default(),
2063 },
2064 },
2065 );
2066
2067 let memory_package = super::Package::from_in_memory(
2068 manifest,
2069 volumes,
2070 atoms,
2071 MemoryVolume {
2072 node: MemoryDir {
2073 modified: SystemTime::UNIX_EPOCH,
2074 nodes: {
2075 let mut children = BTreeMap::new();
2076
2077 children.insert(
2078 "README.md".to_owned(),
2079 MemoryNode::File(MemoryFile {
2080 modified: temp.path().join("README.md").metadata()?.modified()?,
2081 data: b"readme".to_vec(),
2082 }),
2083 );
2084
2085 children.insert(
2086 "LICENSE".to_owned(),
2087 MemoryNode::File(MemoryFile {
2088 modified: temp.path().join("LICENSE").metadata()?.modified()?,
2089 data: b"license".to_vec(),
2090 }),
2091 );
2092
2093 children.insert(
2094 "asdf.wai".to_owned(),
2095 MemoryNode::File(MemoryFile {
2096 modified: temp.path().join("asdf.wai").metadata()?.modified()?,
2097 data: b"exports".to_vec(),
2098 }),
2099 );
2100
2101 children.insert(
2102 "browser.wai".to_owned(),
2103 MemoryNode::File(MemoryFile {
2104 modified: temp.path().join("browser.wai").metadata()?.modified()?,
2105 data: b"imports".to_vec(),
2106 }),
2107 );
2108
2109 children
2110 },
2111 },
2112 },
2113 Strictness::Lossy,
2114 )?;
2115
2116 let memory_package = memory_package.serialize()?;
2117 let fs_package = fs_package.serialize()?;
2118
2119 assert_eq!(memory_package, fs_package);
2120
2121 Ok(())
2122 }
2123}