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