wasmer_wasix/bin_factory/
binary_package.rs1use std::{path::Path, sync::Arc};
2
3use anyhow::Context;
4use once_cell::sync::OnceCell;
5use sha2::Digest;
6use virtual_fs::UnionFileSystem;
7use wasmer_config::package::{
8 PackageHash, PackageId, PackageSource, SuggestedCompilerOptimizations,
9};
10use wasmer_package::package::Package;
11use webc::Container;
12use webc::compat::SharedBytes;
13
14use crate::{
15 Runtime,
16 runners::MappedDirectory,
17 runtime::resolver::{PackageInfo, ResolveError},
18};
19use wasmer_types::ModuleHash;
20
21#[derive(derive_more::Debug, Clone)]
22pub struct BinaryPackageCommand {
23 name: String,
24 metadata: webc::metadata::Command,
25 #[debug(ignore)]
26 pub(crate) atom: SharedBytes,
27 hash: ModuleHash,
28 features: Option<wasmer_types::Features>,
29 pub suggested_compiler_optimizations: SuggestedCompilerOptimizations,
30}
31
32impl BinaryPackageCommand {
33 pub fn new(
34 name: String,
35 metadata: webc::metadata::Command,
36 atom: SharedBytes,
37 hash: ModuleHash,
38 features: Option<wasmer_types::Features>,
39 suggested_compiler_optimizations: SuggestedCompilerOptimizations,
40 ) -> Self {
41 Self {
42 name,
43 metadata,
44 atom,
45 hash,
46 features,
47 suggested_compiler_optimizations,
48 }
49 }
50
51 pub fn name(&self) -> &str {
52 &self.name
53 }
54
55 pub fn metadata(&self) -> &webc::metadata::Command {
56 &self.metadata
57 }
58
59 pub fn atom(&self) -> SharedBytes {
62 self.atom.clone()
63 }
64
65 pub fn hash(&self) -> &ModuleHash {
66 &self.hash
67 }
68
69 pub fn wasm_features(&self) -> Option<wasmer_types::Features> {
71 if let Some(features) = &self.features {
73 return Some(features.clone());
74 }
75
76 None
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct BinaryPackage {
84 pub id: PackageId,
85 pub package_ids: Vec<PackageId>,
87
88 pub when_cached: Option<u128>,
89 pub entrypoint_cmd: Option<String>,
92 pub hash: OnceCell<ModuleHash>,
93 pub webc_fs: Option<Arc<UnionFileSystem>>,
97 pub commands: Vec<BinaryPackageCommand>,
98 pub uses: Vec<String>,
99 pub file_system_memory_footprint: u64,
100
101 pub additional_host_mapped_directories: Vec<MappedDirectory>,
102}
103
104impl BinaryPackage {
105 #[tracing::instrument(level = "debug", skip_all)]
106 pub async fn from_dir(
107 dir: &Path,
108 rt: &(dyn Runtime + Send + Sync),
109 ) -> Result<Self, anyhow::Error> {
110 let source = rt.source();
111
112 let hash = sha2::Sha256::digest(dir.display().to_string().as_bytes()).into();
115 let id = PackageId::Hash(PackageHash::from_sha256_bytes(hash));
116
117 let manifest_path = dir.join("wasmer.toml");
118 let webc = Package::from_manifest(&manifest_path)?;
119 let container = Container::from(webc);
120 let manifest = container.manifest();
121
122 let root = PackageInfo::from_manifest(id, manifest, container.version())?;
123 let root_id = root.id.clone();
124
125 let resolution = crate::runtime::resolver::resolve(&root_id, &root, &*source).await?;
126 let mut pkg = rt
127 .package_loader()
128 .load_package_tree(&container, &resolution, true)
129 .await
130 .map_err(|e| anyhow::anyhow!(e))?;
131
132 let wasmer_toml = std::fs::read_to_string(&manifest_path).unwrap();
135 let wasmer_toml: wasmer_config::package::Manifest = toml::from_str(&wasmer_toml).unwrap();
136 pkg.additional_host_mapped_directories.extend(
137 wasmer_toml
138 .fs
139 .into_iter()
140 .map(|(guest, host)| {
141 anyhow::Ok(MappedDirectory {
142 host: dir.join(host).canonicalize()?,
143 guest,
144 })
145 })
146 .collect::<Result<Vec<_>, _>>()?
147 .into_iter(),
148 );
149
150 Ok(pkg)
151 }
152
153 #[tracing::instrument(level = "debug", skip_all)]
156 pub async fn from_webc(
157 container: &Container,
158 rt: &(dyn Runtime + Send + Sync),
159 ) -> Result<Self, anyhow::Error> {
160 let source = rt.source();
161
162 let manifest = container.manifest();
163 let id = PackageInfo::package_id_from_manifest(manifest)?
164 .or_else(|| {
165 container
166 .webc_hash()
167 .map(|hash| PackageId::Hash(PackageHash::from_sha256_bytes(hash)))
168 })
169 .ok_or_else(|| anyhow::Error::msg("webc file did not provide its hash"))?;
170
171 let root = PackageInfo::from_manifest(id, manifest, container.version())?;
172 let root_id = root.id.clone();
173
174 let resolution = crate::runtime::resolver::resolve(&root_id, &root, &*source).await?;
175 let pkg = rt
176 .package_loader()
177 .load_package_tree(container, &resolution, false)
178 .await
179 .map_err(|e| anyhow::anyhow!(e))?;
180
181 Ok(pkg)
182 }
183
184 #[tracing::instrument(level = "debug", skip_all)]
186 pub async fn from_registry(
187 specifier: &PackageSource,
188 runtime: &(dyn Runtime + Send + Sync),
189 ) -> Result<Self, anyhow::Error> {
190 let source = runtime.source();
191 let root_summary =
192 source
193 .latest(specifier)
194 .await
195 .map_err(|error| ResolveError::Registry {
196 package: specifier.clone(),
197 error,
198 })?;
199 let root = runtime.package_loader().load(&root_summary).await?;
200 let id = root_summary.package_id();
201
202 let resolution = crate::runtime::resolver::resolve(&id, &root_summary.pkg, &source)
203 .await
204 .context("Dependency resolution failed")?;
205 let pkg = runtime
206 .package_loader()
207 .load_package_tree(&root, &resolution, false)
208 .await
209 .map_err(|e| anyhow::anyhow!(e))?;
210
211 Ok(pkg)
212 }
213
214 pub fn get_command(&self, name: &str) -> Option<&BinaryPackageCommand> {
215 self.commands.iter().find(|cmd| cmd.name() == name)
216 }
217
218 pub fn get_entrypoint_command(&self) -> Option<&BinaryPackageCommand> {
220 self.entrypoint_cmd
221 .as_deref()
222 .and_then(|name| self.get_command(name))
223 }
224
225 #[deprecated(
227 note = "Use BinaryPackage::get_entrypoint_command instead",
228 since = "0.22.0"
229 )]
230 pub fn entrypoint_bytes(&self) -> Option<SharedBytes> {
231 self.get_entrypoint_command().map(|entry| entry.atom())
232 }
233
234 pub fn hash(&self) -> ModuleHash {
238 *self.hash.get_or_init(|| {
239 if let Some(cmd) = self.get_entrypoint_command() {
240 cmd.hash
241 } else {
242 ModuleHash::xxhash(self.id.to_string())
243 }
244 })
245 }
246
247 pub fn infer_entrypoint(&self) -> Result<&str, anyhow::Error> {
248 if let Some(entrypoint) = self.entrypoint_cmd.as_deref() {
249 return Ok(entrypoint);
250 }
251
252 match self.commands.as_slice() {
253 [] => anyhow::bail!("The package doesn't contain any executable commands"),
254 [one] => Ok(one.name()),
255 [..] => {
256 let mut commands: Vec<_> = self.commands.iter().map(|cmd| cmd.name()).collect();
257 commands.sort();
258 anyhow::bail!(
259 "Unable to determine the package's entrypoint. Please choose one of {commands:?}"
260 );
261 }
262 }
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use sha2::Digest;
269 use tempfile::TempDir;
270 use virtual_fs::{AsyncReadExt, FileSystem as _};
271 use wasmer_package::utils::from_disk;
272
273 use crate::{
274 PluggableRuntime,
275 runtime::{package_loader::BuiltinPackageLoader, task_manager::VirtualTaskManager},
276 };
277
278 use super::*;
279
280 fn task_manager() -> Arc<dyn VirtualTaskManager + Send + Sync> {
281 cfg_if::cfg_if! {
282 if #[cfg(feature = "sys-thread")] {
283 Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::new(tokio::runtime::Handle::current()))
284 } else {
285 unimplemented!("Unable to get the task manager")
286 }
287 }
288 }
289
290 #[tokio::test]
291 #[cfg_attr(
292 not(feature = "sys-thread"),
293 ignore = "The tokio task manager isn't available on this platform"
294 )]
295 async fn fs_table_can_map_directories_to_different_names() {
296 let temp = TempDir::new().unwrap();
297 let wasmer_toml = r#"
298 [package]
299 name = "some/package"
300 version = "0.0.0"
301 description = "a dummy package"
302
303 [fs]
304 "/public" = "./out"
305 "#;
306 let manifest = temp.path().join("wasmer.toml");
307 std::fs::write(&manifest, wasmer_toml).unwrap();
308 let out = temp.path().join("out");
309 std::fs::create_dir_all(&out).unwrap();
310 let file_txt = "Hello, World!";
311 std::fs::write(out.join("file.txt"), file_txt).unwrap();
312 let tasks = task_manager();
313 let mut runtime = PluggableRuntime::new(tasks);
314 runtime.set_package_loader(
315 BuiltinPackageLoader::new()
316 .with_shared_http_client(runtime.http_client().unwrap().clone()),
317 );
318
319 let pkg = Package::from_manifest(&manifest).unwrap();
320 let data = pkg.serialize().unwrap();
321 let webc_path = temp.path().join("package.webc");
322 std::fs::write(&webc_path, data).unwrap();
323
324 let pkg = BinaryPackage::from_webc(&from_disk(&webc_path).unwrap(), &runtime)
325 .await
326 .unwrap();
327
328 let mut f = pkg
331 .webc_fs
332 .as_ref()
333 .expect("no webc fs")
334 .new_open_options()
335 .read(true)
336 .open("/public/file.txt")
337 .unwrap();
338 let mut buffer = String::new();
339 f.read_to_string(&mut buffer).await.unwrap();
340 assert_eq!(buffer, file_txt);
341 }
342
343 #[tokio::test]
344 #[cfg_attr(
345 not(feature = "sys-thread"),
346 ignore = "The tokio task manager isn't available on this platform"
347 )]
348 async fn commands_use_the_atom_signature() {
349 let temp = TempDir::new().unwrap();
350 let wasmer_toml = r#"
351 [package]
352 name = "some/package"
353 version = "0.0.0"
354 description = "a dummy package"
355
356 [[module]]
357 name = "foo"
358 source = "foo.wasm"
359 abi = "wasi"
360
361 [[command]]
362 name = "cmd"
363 module = "foo"
364 "#;
365 let manifest = temp.path().join("wasmer.toml");
366 std::fs::write(&manifest, wasmer_toml).unwrap();
367
368 let atom_path = temp.path().join("foo.wasm");
369 std::fs::write(&atom_path, b"").unwrap();
370
371 let webc: Container = Package::from_manifest(&manifest).unwrap().into();
372
373 let tasks = task_manager();
374 let mut runtime = PluggableRuntime::new(tasks);
375 runtime.set_package_loader(
376 BuiltinPackageLoader::new()
377 .with_shared_http_client(runtime.http_client().unwrap().clone()),
378 );
379
380 let pkg = BinaryPackage::from_dir(temp.path(), &runtime)
381 .await
382 .unwrap();
383
384 assert_eq!(pkg.commands.len(), 1);
385 let command = pkg.get_command("cmd").unwrap();
386 let atom_sha256_hash: [u8; 32] = sha2::Sha256::digest(webc.get_atom("foo").unwrap()).into();
387 let module_hash = ModuleHash::sha256_from_bytes(atom_sha256_hash);
388 assert_eq!(command.hash(), &module_hash);
389 }
390}