wasmer_wasix/runtime/resolver/
in_memory_source.rs1use std::{
2 collections::{BTreeMap, HashMap, VecDeque},
3 fs::File,
4 path::{Path, PathBuf},
5};
6
7use anyhow::{Context, Error};
8use wasmer_config::package::{NamedPackageId, PackageHash, PackageId, PackageIdent, PackageSource};
9
10use crate::runtime::resolver::{PackageSummary, QueryError, Source};
11
12#[derive(Debug, Default, Clone, PartialEq, Eq)]
16pub struct InMemorySource {
17 named_packages: BTreeMap<String, Vec<NamedPackageSummary>>,
18 hash_packages: HashMap<PackageHash, PackageSummary>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22struct NamedPackageSummary {
23 ident: NamedPackageId,
24 summary: PackageSummary,
25}
26
27impl InMemorySource {
28 pub fn new() -> Self {
29 InMemorySource::default()
30 }
31
32 pub fn from_directory_tree(dir: impl Into<PathBuf>) -> Result<Self, Error> {
34 let mut source = InMemorySource::default();
35
36 let mut to_check: VecDeque<PathBuf> = VecDeque::new();
37 to_check.push_back(dir.into());
38
39 fn process_entry(
40 path: &Path,
41 source: &mut InMemorySource,
42 to_check: &mut VecDeque<PathBuf>,
43 ) -> Result<(), Error> {
44 let metadata = std::fs::metadata(path).context("Unable to get filesystem metadata")?;
45
46 if metadata.is_dir() {
47 for entry in path.read_dir().context("Unable to read the directory")? {
48 to_check.push_back(entry?.path());
49 }
50 } else if metadata.is_file() {
51 let f = File::open(path).context("Unable to open the file")?;
52 if webc::detect(f).is_ok() {
53 source
54 .add_webc(path)
55 .with_context(|| format!("Unable to load \"{}\"", path.display()))?;
56 }
57 }
58
59 Ok(())
60 }
61
62 while let Some(path) = to_check.pop_front() {
63 process_entry(&path, &mut source, &mut to_check)
64 .with_context(|| format!("Unable to add entries from \"{}\"", path.display()))?;
65 }
66
67 Ok(source)
68 }
69
70 pub fn add(&mut self, summary: PackageSummary) {
74 match summary.pkg.id.clone() {
75 PackageId::Named(ident) => {
76 let pkg_hash = PackageHash::Sha256(wasmer_config::hash::Sha256Hash(
78 summary.dist.webc_sha256.as_bytes(),
79 ));
80 self.hash_packages
81 .entry(pkg_hash)
82 .or_insert_with(|| summary.clone());
83
84 let summaries = self
86 .named_packages
87 .entry(ident.full_name.clone())
88 .or_default();
89 summaries.push(NamedPackageSummary { ident, summary });
90 summaries.sort_by(|left, right| left.ident.version.cmp(&right.ident.version));
91 summaries.dedup_by(|left, right| left.ident.version == right.ident.version);
92 }
93 PackageId::Hash(hash) => {
94 self.hash_packages.insert(hash, summary);
95 }
96 }
97 }
98
99 pub fn add_webc(&mut self, path: impl AsRef<Path>) -> Result<(), Error> {
100 let summary = PackageSummary::from_webc_file(path)?;
101 self.add(summary);
102
103 Ok(())
104 }
105
106 pub fn get(&self, id: &PackageId) -> Option<&PackageSummary> {
107 match id {
108 PackageId::Named(ident) => {
109 self.named_packages
110 .get(&ident.full_name)
111 .and_then(|summaries| {
112 summaries
113 .iter()
114 .find(|s| s.ident.version == ident.version)
115 .map(|s| &s.summary)
116 })
117 }
118 PackageId::Hash(hash) => self.hash_packages.get(hash),
119 }
120 }
121
122 pub fn is_empty(&self) -> bool {
123 self.named_packages.is_empty() && self.hash_packages.is_empty()
124 }
125
126 pub fn len(&self) -> usize {
128 self.hash_packages.len()
131 }
132}
133
134#[async_trait::async_trait]
135impl Source for InMemorySource {
136 #[tracing::instrument(level = "debug", skip_all, fields(%package))]
137 async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
138 match package {
139 PackageSource::Ident(PackageIdent::Named(named)) => {
140 match self.named_packages.get(&named.full_name()) {
141 Some(summaries) => {
142 let matches: Vec<_> = summaries
143 .iter()
144 .filter(|summary| {
145 named.version_or_default().matches(&summary.ident.version)
146 })
147 .map(|n| n.summary.clone())
148 .collect();
149
150 tracing::trace!(
151 matches = ?matches
152 .iter()
153 .map(|summary| summary.pkg.id.to_string())
154 .collect::<Vec<_>>(),
155 "package resolution matches",
156 );
157
158 if matches.is_empty() {
159 return Err(QueryError::NoMatches {
160 query: package.clone(),
161 archived_versions: Vec::new(),
162 });
163 }
164
165 Ok(matches)
166 }
167 None => Err(QueryError::NotFound {
168 query: package.clone(),
169 }),
170 }
171 }
172 PackageSource::Ident(PackageIdent::Hash(hash)) => self
173 .hash_packages
174 .get(hash)
175 .map(|x| vec![x.clone()])
176 .ok_or_else(|| QueryError::NoMatches {
177 query: package.clone(),
178 archived_versions: Vec::new(),
179 }),
180 PackageSource::Url(_) | PackageSource::Path(_) => Err(QueryError::Unsupported {
181 query: package.clone(),
182 }),
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use tempfile::TempDir;
190
191 use crate::runtime::resolver::{
192 Dependency, WebcHash,
193 inputs::{DistributionInfo, FileSystemMapping, PackageInfo},
194 };
195
196 use super::*;
197
198 const PYTHON: &[u8] = include_bytes!(concat!(
199 env!("CARGO_MANIFEST_DIR"),
200 "/../../wasmer-test-files/examples/python-0.1.0.wasmer"
201 ));
202 const COREUTILS_16: &[u8] = include_bytes!(concat!(
203 env!("CARGO_MANIFEST_DIR"),
204 "/../../wasmer-test-files/integration/webc/coreutils-1.0.16-e27dbb4f-2ef2-4b44-b46a-ddd86497c6d7.webc"
205 ));
206 const COREUTILS_11: &[u8] = include_bytes!(concat!(
207 env!("CARGO_MANIFEST_DIR"),
208 "/../../wasmer-test-files/integration/webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc"
209 ));
210 const BASH: &[u8] = include_bytes!(concat!(
211 env!("CARGO_MANIFEST_DIR"),
212 "/../../wasmer-test-files/integration/webc/bash-1.0.16-f097441a-a80b-4e0d-87d7-684918ef4bb6.webc"
213 ));
214
215 #[test]
216 fn load_a_directory_tree() {
217 let temp = TempDir::new().unwrap();
218 std::fs::write(temp.path().join("python-0.1.0.webc"), PYTHON).unwrap();
219 std::fs::write(temp.path().join("coreutils-1.0.16.webc"), COREUTILS_16).unwrap();
220 std::fs::write(temp.path().join("coreutils-1.0.11.webc"), COREUTILS_11).unwrap();
221 let nested = temp.path().join("nested");
222 std::fs::create_dir(&nested).unwrap();
223 let bash = nested.join("bash-1.0.12.webc");
224 std::fs::write(&bash, BASH).unwrap();
225
226 let source = InMemorySource::from_directory_tree(temp.path()).unwrap();
227
228 assert_eq!(
229 source
230 .named_packages
231 .keys()
232 .map(|k| k.as_str())
233 .collect::<Vec<_>>(),
234 ["python", "sharrattj/bash", "sharrattj/coreutils"]
235 );
236 assert_eq!(source.named_packages["sharrattj/coreutils"].len(), 2);
237 assert_eq!(
238 source.named_packages["sharrattj/bash"][0].summary,
239 PackageSummary {
240 pkg: PackageInfo {
241 id: PackageId::Named(
242 NamedPackageId::try_new("sharrattj/bash", "1.0.16").unwrap()
243 ),
244 dependencies: vec![Dependency {
245 alias: "coreutils".to_string(),
246 pkg: "sharrattj/coreutils@^1.0.16".parse().unwrap()
247 }],
248 commands: vec![crate::runtime::resolver::Command {
249 name: "bash".to_string(),
250 }],
251 entrypoint: Some("bash".to_string()),
252 filesystem: vec![FileSystemMapping {
253 volume_name: "atom".to_string(),
254 mount_path: "/".to_string(),
255 original_path: Some("/".to_string()),
256 dependency_name: None,
257 }],
258 },
259 dist: DistributionInfo {
260 webc: crate::runtime::resolver::utils::url_from_file_path(
261 bash.canonicalize().unwrap()
262 )
263 .unwrap(),
264 webc_sha256: WebcHash::from_bytes([
265 161, 101, 23, 194, 244, 92, 186, 213, 143, 33, 200, 128, 238, 23, 185, 174,
266 180, 195, 144, 145, 78, 17, 227, 159, 118, 64, 83, 153, 0, 205, 253, 215,
267 ]),
268 },
269 }
270 );
271 }
272}