wasmer_package/convert/
webc_to_package.rs1use std::path::Path;
2
3use wasmer_config::package::{ModuleReference, SuggestedCompilerOptimizations, UserAnnotations};
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::msg(format!("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::msg(format!("could not 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 let mut annotations = None;
154
155 if let Some(manifest_atom) = manifest.atoms.get(&atom_name)
156 && let Some(sco) = manifest_atom
157 .annotations
158 .get(SuggestedCompilerOptimizations::KEY)
159 && let Some((_, v)) = sco.as_map().and_then(|v| {
160 v.iter().find(|(k, _)| {
161 k.as_text()
162 .is_some_and(|v| v == SuggestedCompilerOptimizations::PASS_PARAMS_KEY)
163 })
164 })
165 {
166 annotations = Some(UserAnnotations {
167 suggested_compiler_optimizations: SuggestedCompilerOptimizations {
168 pass_params: Some(v.as_bool().unwrap_or_default()),
169 },
170 });
171 }
172
173 pkg_manifest.modules.push(wasmer_config::package::Module {
174 name: atom_name,
175 source: relative_path.into(),
176 abi: wasmer_config::package::Abi::None,
177 kind: None,
178 interfaces: None,
179 bindings: None,
180 annotations,
181 });
182 }
183 }
184
185 for (name, spec) in &manifest.commands {
187 let mut annotations = toml::Table::new();
188 for (key, value) in &spec.annotations {
189 if key == webc::metadata::annotations::Atom::KEY {
190 continue;
191 }
192
193 let raw_toml = toml::to_string(&value).unwrap();
194 let toml_value = toml::from_str::<toml::Value>(&raw_toml).unwrap();
195 annotations.insert(key.into(), toml_value);
196 }
197
198 let atom_annotation = spec
199 .annotation::<webc::metadata::annotations::Atom>(webc::metadata::annotations::Atom::KEY)
200 .map_err(|err| {
201 ConversionError::msg(format!(
202 "could not read atom annotation for command '{name}': {err}"
203 ))
204 })?
205 .ok_or_else(|| {
206 ConversionError::msg(format!(
207 "Command '{name}' is missing the required atom annotation"
208 ))
209 })?;
210
211 let module = if let Some(dep) = atom_annotation.dependency {
212 ModuleReference::Dependency {
213 dependency: dep,
214 module: atom_annotation.name,
215 }
216 } else {
217 ModuleReference::CurrentPackage {
218 module: atom_annotation.name,
219 }
220 };
221
222 let cmd = wasmer_config::package::Command::V2(wasmer_config::package::CommandV2 {
223 name: name.clone(),
224 module,
225 runner: spec.runner.clone(),
226 annotations: Some(wasmer_config::package::CommandAnnotations::Raw(
227 annotations.into(),
228 )),
229 });
230
231 pkg_manifest.commands.push(cmd);
232 }
233
234 let manifest_toml = toml::to_string(&pkg_manifest)
236 .map_err(|err| ConversionError::with_cause("could not serialize package manifest", err))?;
237 std::fs::write(target_dir.join("wasmer.toml"), manifest_toml)
238 .map_err(|err| ConversionError::with_cause("could not write wasmer.toml", err))?;
239
240 Ok(())
241}
242
243#[cfg(test)]
244mod tests {
245 use std::fs::create_dir_all;
246
247 use pretty_assertions::assert_eq;
248
249 use crate::{package::Package, utils::from_bytes};
250
251 use super::*;
252
253 #[test]
256 fn test_wasmer_package_webc_roundtrip() {
257 let tmpdir = tempfile::tempdir().unwrap();
258 let dir = tmpdir.path();
259
260 let webc = {
261 let dir_input = dir.join("input");
262 let dir_public = dir_input.join("public");
263
264 create_dir_all(&dir_public).unwrap();
265
266 std::fs::write(dir_public.join("index.html"), "INDEX").unwrap();
267
268 std::fs::write(dir_input.join("mywasm.wasm"), "()").unwrap();
269
270 std::fs::write(
271 dir_input.join("wasmer.toml"),
272 r#"
273[package]
274name = "testns/testpkg"
275version = "0.0.1"
276description = "descr1"
277license = "MIT"
278
279[dependencies]
280"wasmer/python" = "8.12.0"
281
282[fs]
283public = "./public"
284
285[[module]]
286name = "mywasm"
287source = "./mywasm.wasm"
288
289[[command]]
290name = "run"
291module = "mywasm"
292runner = "wasi"
293
294[command.annotations.wasi]
295env = ["A=B"]
296main-args = ["/mounted/script.py"]
297"#,
298 )
299 .unwrap();
300
301 let pkg = Package::from_manifest(dir_input.join("wasmer.toml")).unwrap();
302 let raw = pkg.serialize().unwrap();
303 from_bytes(raw).unwrap()
304 };
305
306 let dir_output = dir.join("output");
307 webc_to_package_dir(&webc, &dir_output).unwrap();
308
309 assert_eq!(
310 std::fs::read_to_string(dir_output.join("public/index.html")).unwrap(),
311 "INDEX",
312 );
313
314 assert_eq!(
315 std::fs::read_to_string(dir_output.join("modules/mywasm")).unwrap(),
316 "()",
317 );
318
319 assert_eq!(
320 std::fs::read_to_string(dir_output.join("wasmer.toml"))
321 .unwrap()
322 .trim(),
323 r#"
324
325[package]
326license = "MIT"
327entrypoint = "run"
328
329[dependencies]
330"wasmer/python" = "^8.12.0"
331
332[fs]
333"/public" = "./public"
334
335[[module]]
336name = "mywasm"
337source = "./modules/mywasm"
338
339[[command]]
340name = "run"
341module = "mywasm"
342runner = "https://webc.org/runner/wasi"
343
344[command.annotations.wasi]
345atom = "mywasm"
346env = ["A=B"]
347main-args = ["/mounted/script.py"]
348 "#
349 .trim(),
350 );
351 }
352}