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