wasmer_package/convert/
webc_to_package.rs1use std::path::Path;
2
3use wasmer_config::package::ModuleReference;
4
5use webc::Container;
6
7use super::ConversionError;
8
9pub fn webc_to_package_dir(webc: &Container, target_dir: &Path) -> Result<(), ConversionError> {
12 let mut pkg_manifest = wasmer_config::package::Manifest::new_empty();
13
14 let manifest = webc.manifest();
15 let pkg_annotation = manifest
18 .wapm()
19 .map_err(|err| ConversionError::with_cause("could not read package annotation", err))?;
20 if let Some(ann) = pkg_annotation {
21 let mut pkg = wasmer_config::package::Package::new_empty();
22
23 pkg.name = ann.name;
24 pkg.version = if let Some(raw) = ann.version {
25 let v = raw
26 .parse()
27 .map_err(|e| ConversionError::with_cause("invalid package version", e))?;
28 Some(v)
29 } else {
30 None
31 };
32
33 pkg.description = ann.description;
34 pkg.license = ann.license;
35
36 pkg.homepage = ann.homepage;
39 pkg.repository = ann.repository;
40 pkg.private = ann.private;
41 pkg.entrypoint = manifest.entrypoint.clone();
42
43 pkg_manifest.package = Some(pkg);
44 }
45
46 for (_name, target) in &manifest.use_map {
48 match target {
49 webc::metadata::UrlOrManifest::Url(_url) => {
50 }
52 webc::metadata::UrlOrManifest::Manifest(_) => {
53 }
55 webc::metadata::UrlOrManifest::RegistryDependentUrl(raw) => {
56 let (name, version) = if let Some((name, version_raw)) = raw.split_once('@') {
57 let version = version_raw.parse().map_err(|err| {
58 ConversionError::with_cause(
59 format!("Could not parse version of dependency: '{raw}'"),
60 err,
61 )
62 })?;
63 (name.to_string(), version)
64 } else {
65 (raw.to_string(), "*".parse().unwrap())
66 };
67
68 pkg_manifest.dependencies.insert(name, version);
69 }
70 }
71 }
72
73 let fs_annotation = manifest
76 .filesystem()
77 .map_err(|err| ConversionError::with_cause("could n ot read fs annotation", err))?;
78 if let Some(ann) = fs_annotation {
79 for mapping in ann.0 {
80 if mapping.from.is_some() {
81 continue;
83 }
84
85 let volume = webc.get_volume(&mapping.volume_name).ok_or_else(|| {
87 ConversionError::msg(format!(
88 "Package annotations specify a volume that does not exist: '{}'",
89 mapping.volume_name
90 ))
91 })?;
92
93 let volume_path = target_dir.join(mapping.volume_name.trim_start_matches('/'));
94
95 std::fs::create_dir_all(&volume_path).map_err(|err| {
96 ConversionError::with_cause(
97 format!(
98 "could not create volume directory '{}'",
99 volume_path.display()
100 ),
101 err,
102 )
103 })?;
104
105 volume.unpack("/", &volume_path).map_err(|err| {
106 ConversionError::with_cause("could not unpack volume to filesystemt", err)
107 })?;
108
109 let mut source_path = mapping
110 .volume_name
111 .trim_start_matches('/')
112 .trim_end_matches('/')
113 .to_string();
114 if let Some(subpath) = mapping.host_path {
115 if !source_path.ends_with('/') {
116 source_path.push('/');
117 }
118 source_path.push_str(&subpath);
119 }
120 source_path.insert_str(0, "./");
121
122 pkg_manifest
123 .fs
124 .insert(mapping.mount_path, source_path.into());
125 }
126 }
127
128 let module_dir_name = "modules";
131 let module_dir = target_dir.join(module_dir_name);
132
133 let atoms = webc.atoms();
134 if !atoms.is_empty() {
135 std::fs::create_dir_all(&module_dir).map_err(|err| {
136 ConversionError::with_cause(
137 format!("Could not create directory '{}'", module_dir.display()),
138 err,
139 )
140 })?;
141 for (atom_name, data) in atoms {
142 let atom_path = module_dir.join(&atom_name);
143
144 std::fs::write(&atom_path, &data).map_err(|err| {
145 ConversionError::with_cause(
146 format!("Could not write atom to path '{}'", atom_path.display()),
147 err,
148 )
149 })?;
150
151 let relative_path = format!("./{module_dir_name}/{atom_name}");
152
153 pkg_manifest.modules.push(wasmer_config::package::Module {
154 name: atom_name,
155 source: relative_path.into(),
156 abi: wasmer_config::package::Abi::None,
157 kind: None,
158 interfaces: None,
159 bindings: None,
160 });
161 }
162 }
163
164 for (name, spec) in &manifest.commands {
166 let mut annotations = toml::Table::new();
167 for (key, value) in &spec.annotations {
168 if key == webc::metadata::annotations::Atom::KEY {
169 continue;
170 }
171
172 let raw_toml = toml::to_string(&value).unwrap();
173 let toml_value = toml::from_str::<toml::Value>(&raw_toml).unwrap();
174 annotations.insert(key.into(), toml_value);
175 }
176
177 let atom_annotation = spec
178 .annotation::<webc::metadata::annotations::Atom>(webc::metadata::annotations::Atom::KEY)
179 .map_err(|err| {
180 ConversionError::with_cause(
181 format!("could not read atom annotation for command '{name}'"),
182 err,
183 )
184 })?
185 .ok_or_else(|| {
186 ConversionError::msg(format!(
187 "Command '{name}' is missing the required atom annotation"
188 ))
189 })?;
190
191 let module = if let Some(dep) = atom_annotation.dependency {
192 ModuleReference::Dependency {
193 dependency: dep,
194 module: atom_annotation.name,
195 }
196 } else {
197 ModuleReference::CurrentPackage {
198 module: atom_annotation.name,
199 }
200 };
201
202 let cmd = wasmer_config::package::Command::V2(wasmer_config::package::CommandV2 {
203 name: name.clone(),
204 module,
205 runner: spec.runner.clone(),
206 annotations: Some(wasmer_config::package::CommandAnnotations::Raw(
207 annotations.into(),
208 )),
209 });
210
211 pkg_manifest.commands.push(cmd);
212 }
213
214 let manifest_toml = toml::to_string(&pkg_manifest)
216 .map_err(|err| ConversionError::with_cause("could not serialize package manifest", err))?;
217 std::fs::write(target_dir.join("wasmer.toml"), manifest_toml)
218 .map_err(|err| ConversionError::with_cause("could not write wasmer.toml", err))?;
219
220 Ok(())
221}
222
223#[cfg(test)]
224mod tests {
225 use std::fs::create_dir_all;
226
227 use pretty_assertions::assert_eq;
228
229 use crate::{package::Package, utils::from_bytes};
230
231 use super::*;
232
233 #[test]
236 fn test_wasmer_package_webc_roundtrip() {
237 let tmpdir = tempfile::tempdir().unwrap();
238 let dir = tmpdir.path();
239
240 let webc = {
241 let dir_input = dir.join("input");
242 let dir_public = dir_input.join("public");
243
244 create_dir_all(&dir_public).unwrap();
245
246 std::fs::write(dir_public.join("index.html"), "INDEX").unwrap();
247
248 std::fs::write(dir_input.join("mywasm.wasm"), "()").unwrap();
249
250 std::fs::write(
251 dir_input.join("wasmer.toml"),
252 r#"
253[package]
254name = "testns/testpkg"
255version = "0.0.1"
256description = "descr1"
257license = "MIT"
258
259[dependencies]
260"wasmer/python" = "8.12.0"
261
262[fs]
263public = "./public"
264
265[[module]]
266name = "mywasm"
267source = "./mywasm.wasm"
268
269[[command]]
270name = "run"
271module = "mywasm"
272runner = "wasi"
273
274[command.annotations.wasi]
275env = ["A=B"]
276main-args = ["/mounted/script.py"]
277"#,
278 )
279 .unwrap();
280
281 let pkg = Package::from_manifest(dir_input.join("wasmer.toml")).unwrap();
282 let raw = pkg.serialize().unwrap();
283 from_bytes(raw).unwrap()
284 };
285
286 let dir_output = dir.join("output");
287 webc_to_package_dir(&webc, &dir_output).unwrap();
288
289 assert_eq!(
290 std::fs::read_to_string(dir_output.join("public/index.html")).unwrap(),
291 "INDEX",
292 );
293
294 assert_eq!(
295 std::fs::read_to_string(dir_output.join("modules/mywasm")).unwrap(),
296 "()",
297 );
298
299 assert_eq!(
300 std::fs::read_to_string(dir_output.join("wasmer.toml"))
301 .unwrap()
302 .trim(),
303 r#"
304
305[package]
306license = "MIT"
307entrypoint = "run"
308
309[dependencies]
310"wasmer/python" = "^8.12.0"
311
312[fs]
313"/public" = "./public"
314
315[[module]]
316name = "mywasm"
317source = "./modules/mywasm"
318
319[[command]]
320name = "run"
321module = "mywasm"
322runner = "https://webc.org/runner/wasi"
323
324[command.annotations.wasi]
325atom = "mywasm"
326env = ["A=B"]
327main-args = ["/mounted/script.py"]
328 "#
329 .trim(),
330 );
331 }
332}