#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub extern crate webc;
mod transform;
pub use crate::transform::{Transform, TransformManifestFunctions};
use anyhow::Context;
use base64::{prelude::BASE64_STANDARD, Engine};
use indexmap::IndexMap;
use sha2::{Digest, Sha256};
use std::{
collections::{BTreeMap, BTreeSet},
error::Error,
fmt::{self, Debug, Display},
io::{Error as IoError, Read},
path::Path,
str::FromStr,
};
use url::Url;
use webc::{
metadata::{Atom, Manifest, UrlOrManifest},
v1::{DirOrFile, DirOrFileWithBytes},
v2::{
write::{DirEntry, Directory, FileEntry, Writer},
ChecksumAlgorithm, SignatureAlgorithm,
},
PathSegment,
};
pub type FileMap = BTreeMap<DirOrFile, Vec<u8>>;
pub fn convert_targz_to_pirita(inpath: &Path, outpath: &Path) -> Result<(), anyhow::Error> {
let functions = TransformManifestFunctions::default();
let infile = std::fs::read(inpath)?;
let unpacked = unpack_tar_gz(infile)?;
let base_path = inpath.parent().unwrap_or(Path::new("/")).to_path_buf();
let webc = generate_webc_file(unpacked, &base_path, &functions)?;
std::fs::write(outpath, webc)?;
Ok(())
}
#[derive(Debug)]
pub enum ConvertError {
Io(String, IoError),
Other(anyhow::Error),
}
impl From<anyhow::Error> for ConvertError {
fn from(e: anyhow::Error) -> ConvertError {
ConvertError::Other(e)
}
}
impl Display for ConvertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConvertError::Io(path, e) => write!(f, "{path}: {e}"),
ConvertError::Other(o) => Display::fmt(o, f),
}
}
}
impl Error for ConvertError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ConvertError::Io(_, e) => Some(e),
ConvertError::Other(_) => None,
}
}
}
pub fn unpack_tar_gz(bytes: Vec<u8>) -> Result<FileMap, ConvertError> {
use flate2::read::GzDecoder;
use std::io::Cursor;
use tar::{Archive, EntryType};
let cursor = Cursor::new(bytes);
let mut archive = Archive::new(GzDecoder::new(cursor));
let mut files = BTreeMap::default();
let entries = archive.entries().context("Unable to read the entries")?;
for file in entries {
let mut file = file.context("Entry parsing failed")?;
let file_type = file.header().entry_type();
let path = file
.path()
.map_err(|e| {
ConvertError::Io(String::from_utf8_lossy(&file.path_bytes()).to_string(), e)
})?
.into_owned();
let path = match file_type {
EntryType::Regular => DirOrFile::File(path),
EntryType::Directory => DirOrFile::Dir(path),
e => {
return Err(ConvertError::Other(anyhow::anyhow!(
"Invalid file_type for path \"{}\": {:?}",
path.display(),
e
)));
}
};
let bytes = match &path {
DirOrFile::File(path) => {
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|e| ConvertError::Io(path.display().to_string(), e))?;
buffer
}
DirOrFile::Dir(_) => Vec::new(),
};
files.insert(path, bytes);
}
Ok(files)
}
pub fn generate_webc_file_unpacked(files: FileMap) -> Result<Vec<u8>, anyhow::Error> {
let manifest = get_json_manifest(&files, &files)?;
let (files, atoms, metadata_files) = if manifest.use_map.is_empty() {
let atoms = read_folder(&files, "atoms");
let metadata_files = read_folder(&files, "metadata");
let other_files = read_folder(&files, "atom");
(other_files, atoms, metadata_files)
} else {
let other_files = read_folder(&files, "self::atom");
let atoms = read_folder(&files, "self::atoms");
let metadata_files = read_folder(&files, "self::metadata");
(other_files, atoms, metadata_files)
};
let mut writer = Writer::new(ChecksumAlgorithm::None)
.write_manifest(&manifest)
.context("could not write webc manifest")?
.write_atoms(convert_atoms_webc(atoms))
.context("could not write webc atoms")?;
writer
.write_volume("atom", convert_dir_or_file_to_dir_tree(files)?)
.context("could not write webc volume \"atoms\"")?;
writer
.write_volume("metadata", convert_dir_or_file_to_dir_tree(metadata_files)?)
.context("could not write webc volume \"metadata\"")?;
let bytes = writer
.finish(SignatureAlgorithm::None) .context("could not generate webc file")?;
Ok(bytes.into())
}
pub fn convert_dir_or_file_to_dir_tree_set(
input: BTreeSet<DirOrFileWithBytes<'_>>,
) -> Result<Directory<'_>, anyhow::Error> {
let files = input
.into_iter()
.map(|i| match i {
DirOrFileWithBytes::Dir { path } => (DirOrFile::Dir(path), Vec::new()),
DirOrFileWithBytes::File { path, bytes } => (DirOrFile::Dir(path), bytes.to_vec()),
})
.collect();
convert_dir_or_file_to_dir_tree(files)
}
fn convert_dir_or_file_to_dir_tree<'a>(
input: BTreeMap<DirOrFile, Vec<u8>>,
) -> Result<Directory<'a>, anyhow::Error> {
let mut dir = Directory::default();
for (k, v) in input {
insert_into_dir(&mut dir, k, v);
}
Ok(dir)
}
fn insert_into_dir(dir: &mut Directory, path: DirOrFile, bytes: Vec<u8>) {
let components = path.components();
let mut reference = &mut dir.children;
for dir in components.iter().take(components.len().saturating_sub(1)) {
let path_segment = PathSegment::from_str(dir).unwrap();
if reference.get(&path_segment).is_none() {
reference.insert(path_segment.clone(), DirEntry::Dir(Directory::default()));
}
reference = match reference.get_mut(&path_segment).unwrap() {
DirEntry::Dir(d) => &mut d.children,
_ => return,
};
}
if let Some(last) = components.last() {
let path_segment = PathSegment::from_str(last).unwrap();
if path.is_dir() {
reference.insert(path_segment, DirEntry::Dir(Directory::default()));
} else {
reference.insert(path_segment, DirEntry::File(bytes.into()));
}
}
}
pub fn convert_atoms_webc<'a>(
input: BTreeMap<DirOrFile, Vec<u8>>,
) -> BTreeMap<PathSegment, FileEntry<'a>> {
let mut map = BTreeMap::new();
for (k, v) in input {
let components = k.get_path_buf().display().to_string().replace('\\', "/");
let path_segment = match PathSegment::from_str(&components) {
Ok(o) => o,
Err(_) => continue,
};
assert!(
!k.is_dir(),
"The atoms section should only ever contain files"
);
map.insert(path_segment, v.into());
}
map
}
fn read_folder(files: &FileMap, folder: impl AsRef<Path>) -> FileMap {
let folder = folder.as_ref();
files
.iter()
.filter_map(|(k, v)| {
let item_path = k.get_path_buf();
if let Ok(filename) = item_path.strip_prefix(folder) {
let path = match k {
DirOrFile::Dir(_) => DirOrFile::Dir(filename.to_path_buf()),
DirOrFile::File(_) => DirOrFile::File(filename.to_path_buf()),
};
Some((path, v.clone()))
} else {
None
}
})
.collect()
}
pub fn generate_webc_file(
mut files: FileMap,
base_path: &Path,
functions: &dyn Transform,
) -> Result<Vec<u8>, anyhow::Error> {
if files
.get(&DirOrFile::File(Path::new("wapm.v1.json").to_path_buf()))
.is_some()
{
return generate_webc_file_unpacked(files);
}
let wasmer_toml_path = DirOrFile::File("wasmer.toml".into());
if !files.contains_key(&wasmer_toml_path) {
if let Some(wapm_toml) = files.remove(&DirOrFile::File("wapm.toml".into())) {
files.insert(wasmer_toml_path, wapm_toml);
}
}
let atoms = get_atoms(&mut files, true, functions)?;
let manifest = get_manifest(&files, base_path, &atoms, functions)?;
let bindings_paths = get_bindings_paths(&manifest);
let metadata_files = get_metadata(&bindings_paths, &mut files, functions);
let files = get_files(&files, &atoms, &manifest)?;
let mut writer = Writer::new(ChecksumAlgorithm::Sha256)
.write_manifest(&manifest)
.context("could not write webc manifest")?
.write_atoms(convert_atoms_webc(atoms))
.context("could not write webc atoms")?;
writer
.write_volume("atom", convert_dir_or_file_to_dir_tree(files)?)
.context("could not write webc volume \"atoms\"")?;
writer
.write_volume("metadata", convert_dir_or_file_to_dir_tree(metadata_files)?)
.context("could not write webc volume \"metadata\"")?;
let bytes = writer
.finish(SignatureAlgorithm::None) .context("could not generate webc file")?;
Ok(bytes.into())
}
fn get_bindings_paths(manifest: &Manifest) -> Vec<serde_cbor::Value> {
manifest
.bindings
.iter()
.map(|v| v.annotations.clone())
.collect()
}
fn parse_manifest_json_1(s: &str, _: &FileMap, _: &FileMap) -> Result<Manifest, anyhow::Error> {
Ok(serde_json::from_str(s)?)
}
fn parse_manifest_json_2(s: &str, _: &FileMap, _: &FileMap) -> Result<Manifest, anyhow::Error> {
Ok(json5::from_str(s)?)
}
fn parse_manifest_json_3(
s: &str,
files: &FileMap,
atoms: &FileMap,
) -> Result<Manifest, anyhow::Error> {
let parsed = serde_json::from_str(s);
let no_sha1: webc::metadata::ManifestWithoutAtomSignatures = parsed?;
to_manifest(&no_sha1, files, atoms)
}
fn parse_manifest_json_4(
s: &str,
files: &FileMap,
atoms: &FileMap,
) -> Result<Manifest, anyhow::Error> {
let no_sha1: webc::metadata::ManifestWithoutAtomSignatures = json5::from_str(s)?;
to_manifest(&no_sha1, files, atoms)
}
fn to_manifest(
manifest: &webc::metadata::ManifestWithoutAtomSignatures,
files: &FileMap,
atoms: &FileMap,
) -> Result<Manifest, anyhow::Error> {
let atom_sha256s = manifest
.atoms
.keys()
.filter_map(|atom| {
let searched = DirOrFile::File(Path::new(atom).to_path_buf());
let bytes = files.get(&searched).or_else(|| atoms.get(&searched))?;
let sha256 = calc_sha256_base64(bytes);
Some((atom.clone(), format!("sha256:{sha256}")))
})
.collect();
let manifest = manifest.to_manifest(&atom_sha256s)?;
Ok(manifest)
}
fn calc_sha256_base64(input: &[u8]) -> String {
let mut hasher = sha2::Sha256::new();
hasher.update(input);
let result = hasher.finalize().to_vec();
BASE64_STANDARD.encode(result)
}
fn get_wapm_manifest(files: &FileMap, functions: &dyn Transform) -> Result<String, anyhow::Error> {
let wapm_toml = files
.get(&DirOrFile::File(functions.get_wapm_manifest_file_name()))
.and_then(|bytes| String::from_utf8(bytes.clone()).ok())
.ok_or(anyhow::anyhow!("No wapm.toml found in wapm .tar.gz"))?;
Ok(wapm_toml)
}
fn get_metadata(
binding_paths: &[serde_cbor::Value],
files: &mut FileMap,
functions: &dyn Transform,
) -> FileMap {
let mut target = FileMap::default();
for file in functions.get_metadata_paths(binding_paths).iter() {
let target_path = DirOrFile::File(Path::new(file).to_path_buf());
if let Some(file) = files.remove(&target_path) {
target.insert(target_path, file.clone());
}
}
target
}
fn transform_manifest(
wapm: &str,
base_path: &Path,
atom_files: &BTreeMap<DirOrFile, Vec<u8>>,
functions: &dyn Transform,
) -> Result<Manifest, anyhow::Error> {
let mut use_map = IndexMap::new();
for (dep_key, version) in functions.get_dependencies(wapm).iter() {
let dep_path = dep_key.split('/').collect::<Vec<_>>();
if dep_path.len() != 2 {
return Err(anyhow::anyhow!(
"Dependency {dep_path:?} is not in standard owner/name format"
));
}
let dep_org = &dep_path[0];
let dep_name = &dep_path[1];
let url = format!("{dep_org}/{dep_name}@{version}");
use_map.insert(
dep_path[1].to_string(),
UrlOrManifest::RegistryDependentUrl(url),
);
}
let package = package_annotations(wapm, functions)?;
let mut atom_kinds = BTreeMap::new();
let mut atoms = IndexMap::new();
for (name, abi, kind) in functions.get_modules(wapm).iter() {
atom_kinds.insert(name.to_string(), abi.to_string());
if atoms.contains_key(name) {
return Err(anyhow::anyhow!("Module {name} is specified twice"));
}
let sha256 = match atom_files.get(&DirOrFile::File(Path::new(name).to_path_buf())) {
Some(s) => {
let mut hasher = Sha256::new();
hasher.update(s);
let result = hasher.finalize().to_vec();
BASE64_STANDARD.encode(result)
}
None => {
return Err(anyhow::anyhow!("Atom {name:?} not found in files"));
}
};
atoms.insert(
name.clone(),
Atom {
kind: match kind.as_str() {
"wasm" => Url::parse("https://webc.org/kind/wasm").unwrap(),
"tensorflow-SavedModel" => {
Url::parse("https://webc.org/kind/tensorflow-SavedModel").unwrap()
}
_ => {
return Err(anyhow::anyhow!(
"Unknown \"module.kind\": {kind:?} in module {name}"
));
}
},
signature: format!("sha256:{sha256}"),
},
);
}
let commands: IndexMap<String, webc::metadata::Command> = functions
.get_commands(wapm, base_path, &atom_kinds)?
.into_iter()
.map(|(k, cmd)| {
(
k,
webc::metadata::Command {
runner: cmd.runner,
annotations: cmd.annotations.into_iter().collect(),
},
)
})
.collect();
let bindings = functions
.get_bindings(wapm, base_path, &atom_kinds)?
.into_iter()
.map(|(name, kind, annotations)| webc::metadata::Binding {
name,
kind,
annotations,
})
.collect();
let entrypoint = if commands.len() > 1 {
None
} else {
commands.iter().map(|(k, _v)| k.clone()).next()
};
Ok(Manifest {
origin: None,
use_map,
package,
atoms,
bindings,
commands,
entrypoint,
})
}
fn package_annotations(
wapm_toml: &str,
functions: &dyn Transform,
) -> Result<IndexMap<String, serde_cbor::Value>, anyhow::Error> {
let mut package = IndexMap::new();
let wapm = functions.get_package_annotations(wapm_toml)?;
package.insert(
webc::metadata::annotations::Wapm::KEY.to_string(),
serde_cbor::value::to_value(wapm)?,
);
let fs = functions.get_fs_table(wapm_toml)?;
if !fs.is_empty() {
package.insert(
webc::metadata::annotations::FileSystemMappings::KEY.to_string(),
serde_cbor::value::to_value(fs)?,
);
}
Ok(package)
}
fn get_json_manifest(files: &FileMap, atoms: &FileMap) -> Result<Manifest, anyhow::Error> {
let file = files
.get(&DirOrFile::File(Path::new("manifest.json").to_path_buf()))
.or_else(|| files.get(&DirOrFile::File(Path::new("wapm.v1.json").to_path_buf())));
let wapm_json = file
.and_then(|bytes| String::from_utf8(bytes.clone()).ok())
.ok_or(anyhow::anyhow!("No manifest.json found in directory"))?;
let parsers = &[
parse_manifest_json_1(&wapm_json, files, atoms).map_err(|e| format!("JSON: {e}")),
parse_manifest_json_2(&wapm_json, files, atoms).map_err(|e| format!("JSON5: {e}")),
parse_manifest_json_3(&wapm_json, files, atoms).map_err(|e| format!("JSON: {e}")),
parse_manifest_json_4(&wapm_json, files, atoms).map_err(|e| format!("JSON5: {e}")),
];
if let Some(manifest) = parsers.iter().flatten().next() {
return Ok(manifest.clone());
}
let errors = parsers
.iter()
.filter_map(|o| match o {
Err(e) => Some(e.clone()),
Ok(_) => None,
})
.collect::<Vec<_>>();
Err(anyhow::anyhow!(
"Could not parse manifest.json: {errors:#?}"
))
}
fn get_manifest(
files: &FileMap,
base_path: &Path,
atoms: &BTreeMap<DirOrFile, Vec<u8>>,
functions: &dyn Transform,
) -> Result<Manifest, anyhow::Error> {
if let Ok(f) = get_json_manifest(files, atoms) {
Ok(f)
} else {
let wapm_manifest = get_wapm_manifest(files, functions)?;
transform_manifest(&wapm_manifest, base_path, atoms, functions)
}
}
fn get_atoms(
files: &mut FileMap,
remove_atoms: bool,
functions: &dyn Transform,
) -> Result<FileMap, anyhow::Error> {
match get_atoms_wapm_json(files, remove_atoms) {
Ok(o) => Ok(o),
Err(_e) => get_atoms_wapm_toml(files, remove_atoms, functions),
}
}
pub(crate) fn get_atoms_wapm_toml(
files: &mut FileMap,
remove_atoms: bool,
functions: &dyn Transform,
) -> Result<FileMap, anyhow::Error> {
let paths = files
.iter()
.map(|(k, v)| (k.get_path_buf().as_path(), v))
.collect();
let atom_file_paths = functions.get_atoms_wapm_toml(&paths)?;
let mut atom_files = FileMap::default();
for (name, mut path) in atom_file_paths {
let path_display = format!("{}", path.display());
if path_display.starts_with("./") {
path = Path::new(&path_display.replacen("./", "", 1)).to_path_buf();
}
let source = DirOrFile::File(path.clone());
let source_bytes = if remove_atoms {
files.remove(&source).ok_or(anyhow::anyhow!(
"Could not transform module {name}: file {} not found",
path.display()
))
} else {
files.get(&source).cloned().ok_or(anyhow::anyhow!(
"Could not transform module {name}: file {} not found",
path.display()
))
}?;
let target = DirOrFile::File(Path::new(&name).to_path_buf());
if atom_files.contains_key(&target) {
return Err(anyhow::anyhow!(
"Duplicate module {name} in wapm.toml manifest"
));
}
atom_files.insert(target, source_bytes);
}
Ok(atom_files)
}
fn get_atoms_wapm_json(files: &mut FileMap, remove_atoms: bool) -> Result<FileMap, anyhow::Error> {
let manifest = get_json_manifest(files, files)?;
let mut atom_files = FileMap::default();
for atom_name in manifest.atoms.keys() {
let source = DirOrFile::File(Path::new(&atom_name).to_path_buf());
let source_bytes = if remove_atoms {
files.remove(&source).ok_or(anyhow::anyhow!(
"Could not transform atom {atom_name:?}: file {atom_name:?} not found",
))
} else {
files.get(&source).cloned().ok_or(anyhow::anyhow!(
"Could not transform atom {atom_name:?}: file {atom_name:?} not found",
))
}?;
let target = DirOrFile::File(Path::new(atom_name).to_path_buf());
if atom_files.contains_key(&target) {
return Err(anyhow::anyhow!(
"Duplicate atom {atom_name:?} in manifest.json manifest"
));
}
atom_files.insert(target, source_bytes);
}
Ok(atom_files)
}
fn get_files(
files: &FileMap,
atoms: &FileMap,
_manifest: &Manifest,
) -> Result<FileMap, anyhow::Error> {
let mut target = BTreeMap::new();
let mut files_to_add = files.keys().cloned().collect::<BTreeSet<_>>();
files_to_add.remove(&DirOrFile::File(Path::new("wapm.toml").into()));
files_to_add.remove(&DirOrFile::File(Path::new("wasmer.toml").into()));
files_to_add.remove(&DirOrFile::File(Path::new("manifest.json").into()));
let files_not_to_add = atoms.keys().cloned().collect::<BTreeSet<_>>();
let difference = files_to_add.difference(&files_not_to_add);
for key in difference {
let data = files
.get(key)
.ok_or(anyhow::anyhow!("could not get file {key}"))?;
target.insert(key.clone(), data.clone());
}
Ok(target)
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
use webc::{
metadata::annotations::FileSystemMapping,
v2::read::{DirEntry, Directory, OwnedReader},
};
use super::*;
#[test]
fn test_parse_coreutils() {
let targz_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/fixtures/coreutils-1.0.11.tar.gz"
);
let tempdir = TempDir::new().unwrap();
let webc_path = tempdir.path().join("bytes.webc");
crate::convert_targz_to_pirita(Path::new(targz_path), &webc_path).unwrap();
let file = std::fs::read(webc_path).unwrap();
let webc = OwnedReader::parse(file).unwrap();
let atoms: Vec<_> = webc.atom_names().collect();
assert_eq!(atoms, &["coreutils"]);
insta::assert_debug_snapshot!(webc
.iter_atoms()
.map(|(name, data)| (name, data.len()))
.collect::<Vec<_>>());
let mut settings = insta::Settings::clone_current();
settings.add_filter(r"\bC:\\", "/");
settings.add_filter(r"\\\\", "/");
let _guard = settings.bind_to_scope();
let v = webc.get_volume("atom").unwrap();
insta::assert_debug_snapshot!(VolumeSnapshot::from_dir(v.root().unwrap()));
let v = webc.get_volume("metadata").unwrap();
insta::assert_debug_snapshot!(VolumeSnapshot::from_dir(v.root().unwrap()));
let v = webc.get_volume("atom").unwrap();
insta::assert_debug_snapshot!(VolumeSnapshot::from_dir(v.root().unwrap()));
}
#[derive(Debug, PartialEq)]
enum VolumeSnapshot {
Dir(BTreeMap<String, VolumeSnapshot>),
File,
}
impl VolumeSnapshot {
fn from_dir_entry(entry: DirEntry) -> Self {
match entry {
DirEntry::Dir(d) => VolumeSnapshot::from_dir(d),
DirEntry::File(_) => VolumeSnapshot::File,
}
}
fn from_dir(dir: Directory) -> Self {
let mut entries = BTreeMap::new();
for result in dir.entries() {
let (name, entry) = result.unwrap();
entries.insert(name.to_string(), VolumeSnapshot::from_dir_entry(entry));
}
VolumeSnapshot::Dir(entries)
}
}
#[test]
#[should_panic]
fn test_convert_dir_files_webc() {
let _ = convert_atoms_webc(
vec![(DirOrFile::Dir(Path::new("a").to_path_buf()), Vec::new())]
.into_iter()
.collect(),
);
}
#[test]
fn test_convert_dir_files_webc_2() {
let files = vec![(
DirOrFile::File(Path::new("self::name/package@version").to_path_buf()),
Vec::new(),
)];
let converted = convert_atoms_webc(files.into_iter().collect());
assert_eq!(
converted.keys().cloned().collect::<Vec<_>>(),
vec![PathSegment::from_str("self::name/package@version").unwrap()],
);
}
#[test]
fn custom_annotations_are_copied_across_verbatim() {
let wapm = r#"
[package]
name = "test"
version = "0.0.0"
description = "asdf"
[[module]]
name = "module"
source = "file.wasm"
abi = "wasi"
[[command]]
name = "command"
module = "module"
runner = "asdf"
annotations = { first = 42, second = ["a", "b"] }
"#;
let atom_files: BTreeMap<DirOrFile, Vec<u8>> =
[(DirOrFile::File("module".into()), b"\0asm".to_vec())]
.into_iter()
.collect();
let functions = TransformManifestFunctions::default();
let webc_manifest =
transform_manifest(wapm, Path::new("."), &atom_files, &functions).unwrap();
let command = &webc_manifest.commands["command"];
assert_eq!(command.annotation::<u32>("first").unwrap(), Some(42));
assert_eq!(command.annotation::<String>("non-existent").unwrap(), None);
}
macro_rules! files {
($( $name:literal : $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut files: BTreeMap<DirOrFile, Vec<u8>> = BTreeMap::new();
$(
let path = if $name.ends_with('/') {
DirOrFile::Dir($name.into())
} else {
DirOrFile::File($name.into())
};
files.insert(path, $value.to_vec());
)*
files
}};
}
#[test]
fn transform_empty_manifest() {
let wapm = r#"
[package]
name = "some/package"
version = "0.0.0"
description = "My awesome package"
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files!();
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_manifest_with_single_atom() {
let wapm = r#"
[package]
name = "some/package"
version = "0.0.0"
description = "My awesome package"
[[module]]
name = "first"
source = "./path/to/file.wasm"
abi = "wasi"
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files! { "first": b"\0asm..." };
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_manifest_with_atom_and_command() {
let wapm = r#"
[package]
name = "some/package"
version = "0.0.0"
description = "My awesome package"
[[module]]
name = "cpython"
source = "python.wasm"
abi = "wasi"
[[command]]
name = "python"
module = "cpython"
runner = "wasi"
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files! { "cpython": b"\0asm..." };
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_manifest_with_multiple_commands() {
let wapm = r#"
[package]
name = "some/package"
version = "0.0.0"
description = "My awesome package"
[[module]]
name = "cpython"
source = "python.wasm"
abi = "wasi"
[[command]]
name = "first"
module = "cpython"
runner = "wasi"
[[command]]
name = "second"
module = "cpython"
runner = "wasi"
annotations = { wasi = { env = ["KEY=val"]} }
[[command]]
name = "third"
module = "cpython"
runner = "wasi"
annotations = { wcgi = { dialect = "rfc-3875" } }
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files! {"cpython": b"\0asm..."};
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_bash_manifest() {
let wapm = r#"
[package]
name = "sharrattj/bash"
version = "1.0.17"
description = "Bash is a modern POSIX-compliant implementation of /bin/sh."
license = "GNU"
wasmer-extra-flags = "--enable-threads --enable-bulk-memory"
[dependencies]
"sharrattj/coreutils" = "1.0.16"
[[module]]
name = "bash"
source = "bash.wasm"
abi = "wasi"
[[command]]
name = "bash"
module = "bash"
runner = "wasi@unstable_"
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files! { "bash": b"\0asm..." };
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_wasmer_pack_manifest() {
let wapm = r#"
[package]
name = "wasmer/wasmer-pack"
version = "0.7.0"
description = "The WebAssembly interface to wasmer-pack."
license = "MIT"
readme = "/home/runner/work/wasmer-pack/wasmer-pack/crates/wasm/../../README.md"
repository = "https://github.com/wasmerio/wasmer-pack"
homepage = "https://wasmer.io/"
[[module]]
name = "wasmer-pack-wasm"
source = "wasmer_pack_wasm.wasm"
[module.bindings]
wai-version = "0.2.0"
exports = "wasmer-pack.exports.wai"
imports = []
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files! { "wasmer-pack-wasm": b"\0asm..." };
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_python_manifest() {
let wapm = r#"
[package]
name = "python"
version = "0.1.0"
description = "Python is an interpreted, high-level, general-purpose programming language"
license = "ISC"
repository = "https://github.com/wapm-packages/python"
[[module]]
name = "python"
source = "bin/python.wasm"
abi = "wasi"
[module.interfaces]
wasi = "0.0.0-unstable"
[[command]]
name = "python"
module = "python"
[fs]
lib = "lib"
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files! { "python": b"\0asm..." };
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
insta::with_settings! {
{ description => wapm },
{ insta::assert_yaml_snapshot!(&transformed); }
}
}
#[test]
fn transform_manifest_with_fs_table() {
let wapm = r#"
[package]
name = "some/package"
version = "0.0.0"
description = "This is a package"
[fs]
lib = "lib"
"/public" = "./out"
"#;
let base_path = Path::new(".");
let functions = TransformManifestFunctions::default();
let atom_files = files!();
let transformed = transform_manifest(wapm, base_path, &atom_files, &functions).unwrap();
let fs = transformed.filesystem().unwrap().unwrap();
assert_eq!(
fs,
[
FileSystemMapping {
from: None,
volume_name: "atom".to_string(),
host_path: None,
mount_path: "/lib".to_string(),
},
FileSystemMapping {
from: None,
volume_name: "atom".to_string(),
host_path: None,
mount_path: "/public".to_string(),
}
]
)
}
}