wasmer_package/convert/
webc_to_package.rsuse std::path::Path;
use wasmer_config::package::ModuleReference;
use webc::Container;
use super::ConversionError;
pub fn webc_to_package_dir(webc: &Container, target_dir: &Path) -> Result<(), ConversionError> {
let mut pkg_manifest = wasmer_config::package::Manifest::new_empty();
let manifest = webc.manifest();
let pkg_annotation = manifest
.wapm()
.map_err(|err| ConversionError::with_cause("could not read package annotation", err))?;
if let Some(ann) = pkg_annotation {
let mut pkg = wasmer_config::package::Package::new_empty();
pkg.name = ann.name;
pkg.version = if let Some(raw) = ann.version {
let v = raw
.parse()
.map_err(|e| ConversionError::with_cause("invalid package version", e))?;
Some(v)
} else {
None
};
pkg.description = ann.description;
pkg.license = ann.license;
pkg.homepage = ann.homepage;
pkg.repository = ann.repository;
pkg.private = ann.private;
pkg.entrypoint = manifest.entrypoint.clone();
pkg_manifest.package = Some(pkg);
}
for (_name, target) in &manifest.use_map {
match target {
webc::metadata::UrlOrManifest::Url(_url) => {
}
webc::metadata::UrlOrManifest::Manifest(_) => {
}
webc::metadata::UrlOrManifest::RegistryDependentUrl(raw) => {
let (name, version) = if let Some((name, version_raw)) = raw.split_once('@') {
let version = version_raw.parse().map_err(|err| {
ConversionError::with_cause(
format!("Could not parse version of dependency: '{}'", raw),
err,
)
})?;
(name.to_string(), version)
} else {
(raw.to_string(), "*".parse().unwrap())
};
pkg_manifest.dependencies.insert(name, version);
}
}
}
let fs_annotation = manifest
.filesystem()
.map_err(|err| ConversionError::with_cause("could n ot read fs annotation", err))?;
if let Some(ann) = fs_annotation {
for mapping in ann.0 {
if mapping.from.is_some() {
continue;
}
let volume = webc.get_volume(&mapping.volume_name).ok_or_else(|| {
ConversionError::msg(format!(
"Package annotations specify a volume that does not exist: '{}'",
mapping.volume_name
))
})?;
let volume_path = target_dir.join(mapping.volume_name.trim_start_matches('/'));
std::fs::create_dir_all(&volume_path).map_err(|err| {
ConversionError::with_cause(
format!(
"could not create volume directory '{}'",
volume_path.display()
),
err,
)
})?;
volume.unpack("/", &volume_path).map_err(|err| {
ConversionError::with_cause("could not unpack volume to filesystemt", err)
})?;
let mut source_path = mapping
.volume_name
.trim_start_matches('/')
.trim_end_matches('/')
.to_string();
if let Some(subpath) = mapping.host_path {
if !source_path.ends_with('/') {
source_path.push('/');
}
source_path.push_str(&subpath);
}
source_path.insert_str(0, "./");
pkg_manifest
.fs
.insert(mapping.mount_path, source_path.into());
}
}
let module_dir_name = "modules";
let module_dir = target_dir.join(module_dir_name);
let atoms = webc.atoms();
if !atoms.is_empty() {
std::fs::create_dir_all(&module_dir).map_err(|err| {
ConversionError::with_cause(
format!("Could not create directory '{}'", module_dir.display(),),
err,
)
})?;
for (atom_name, data) in atoms {
let atom_path = module_dir.join(&atom_name);
std::fs::write(&atom_path, &data).map_err(|err| {
ConversionError::with_cause(
format!("Could not write atom to path '{}'", atom_path.display()),
err,
)
})?;
let relative_path = format!("./{module_dir_name}/{atom_name}");
pkg_manifest.modules.push(wasmer_config::package::Module {
name: atom_name,
source: relative_path.into(),
abi: wasmer_config::package::Abi::None,
kind: None,
interfaces: None,
bindings: None,
});
}
}
for (name, spec) in &manifest.commands {
let mut annotations = toml::Table::new();
for (key, value) in &spec.annotations {
if key == webc::metadata::annotations::Atom::KEY {
continue;
}
let raw_toml = toml::to_string(&value).unwrap();
let toml_value = toml::from_str::<toml::Value>(&raw_toml).unwrap();
annotations.insert(key.into(), toml_value);
}
let atom_annotation = spec
.annotation::<webc::metadata::annotations::Atom>(webc::metadata::annotations::Atom::KEY)
.map_err(|err| {
ConversionError::with_cause(
format!("could not read atom annotation for command '{}'", name),
err,
)
})?
.ok_or_else(|| {
ConversionError::msg(format!(
"Command '{name}' is missing the required atom annotation"
))
})?;
let module = if let Some(dep) = atom_annotation.dependency {
ModuleReference::Dependency {
dependency: dep,
module: atom_annotation.name,
}
} else {
ModuleReference::CurrentPackage {
module: atom_annotation.name,
}
};
let cmd = wasmer_config::package::Command::V2(wasmer_config::package::CommandV2 {
name: name.clone(),
module,
runner: spec.runner.clone(),
annotations: Some(wasmer_config::package::CommandAnnotations::Raw(
annotations.into(),
)),
});
pkg_manifest.commands.push(cmd);
}
let manifest_toml = toml::to_string(&pkg_manifest)
.map_err(|err| ConversionError::with_cause("could not serialize package manifest", err))?;
std::fs::write(target_dir.join("wasmer.toml"), manifest_toml)
.map_err(|err| ConversionError::with_cause("could not write wasmer.toml", err))?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::fs::create_dir_all;
use pretty_assertions::assert_eq;
use crate::{package::Package, utils::from_bytes};
use super::*;
#[test]
fn test_wasmer_package_webc_roundtrip() {
let tmpdir = tempfile::tempdir().unwrap();
let dir = tmpdir.path();
let webc = {
let dir_input = dir.join("input");
let dir_public = dir_input.join("public");
create_dir_all(&dir_public).unwrap();
std::fs::write(dir_public.join("index.html"), "INDEX").unwrap();
std::fs::write(dir_input.join("mywasm.wasm"), "()").unwrap();
std::fs::write(
dir_input.join("wasmer.toml"),
r#"
[package]
name = "testns/testpkg"
version = "0.0.1"
description = "descr1"
license = "MIT"
[dependencies]
"wasmer/python" = "8.12.0"
[fs]
public = "./public"
[[module]]
name = "mywasm"
source = "./mywasm.wasm"
[[command]]
name = "run"
module = "mywasm"
runner = "wasi"
[command.annotations.wasi]
env = ["A=B"]
main-args = ["/mounted/script.py"]
"#,
)
.unwrap();
let pkg = Package::from_manifest(dir_input.join("wasmer.toml")).unwrap();
let raw = pkg.serialize().unwrap();
from_bytes(raw).unwrap()
};
let dir_output = dir.join("output");
webc_to_package_dir(&webc, &dir_output).unwrap();
assert_eq!(
std::fs::read_to_string(dir_output.join("public/index.html")).unwrap(),
"INDEX",
);
assert_eq!(
std::fs::read_to_string(dir_output.join("modules/mywasm")).unwrap(),
"()",
);
assert_eq!(
std::fs::read_to_string(dir_output.join("wasmer.toml"))
.unwrap()
.trim(),
r#"
[package]
license = "MIT"
entrypoint = "run"
[dependencies]
"wasmer/python" = "^8.12.0"
[fs]
"/public" = "./public"
[[module]]
name = "mywasm"
source = "./modules/mywasm"
[[command]]
name = "run"
module = "mywasm"
runner = "https://webc.org/runner/wasi"
[command.annotations.wasi]
atom = "mywasm"
env = ["A=B"]
main-args = ["/mounted/script.py"]
"#
.trim(),
);
}
}