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
91 .sort_by(|left, right| left.ident.version.cmp_precedence(&right.ident.version));
92 summaries.dedup_by(|left, right| left.ident.version == right.ident.version);
93 }
94 PackageId::Hash(hash) => {
95 self.hash_packages.insert(hash, summary);
96 }
97 }
98 }
99
100 pub fn add_webc(&mut self, path: impl AsRef<Path>) -> Result<(), Error> {
101 let summary = PackageSummary::from_webc_file(path)?;
102 self.add(summary);
103
104 Ok(())
105 }
106
107 pub fn get(&self, id: &PackageId) -> Option<&PackageSummary> {
108 match id {
109 PackageId::Named(ident) => {
110 self.named_packages
111 .get(&ident.full_name)
112 .and_then(|summaries| {
113 summaries
114 .iter()
115 .find(|s| s.ident.version == ident.version)
116 .map(|s| &s.summary)
117 })
118 }
119 PackageId::Hash(hash) => self.hash_packages.get(hash),
120 }
121 }
122
123 pub fn is_empty(&self) -> bool {
124 self.named_packages.is_empty() && self.hash_packages.is_empty()
125 }
126
127 pub fn len(&self) -> usize {
129 self.hash_packages.len()
132 }
133}
134
135#[async_trait::async_trait]
136impl Source for InMemorySource {
137 #[tracing::instrument(level = "debug", skip_all, fields(%package))]
138 async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
139 match package {
140 PackageSource::Ident(PackageIdent::Named(named)) => {
141 match self.named_packages.get(&named.full_name()) {
142 Some(summaries) => {
143 let matches: Vec<_> = summaries
144 .iter()
145 .filter(|summary| {
146 named.version_or_default().matches(&summary.ident.version)
147 })
148 .map(|n| n.summary.clone())
149 .collect();
150
151 tracing::trace!(
152 matches = ?matches
153 .iter()
154 .map(|summary| summary.pkg.id.to_string())
155 .collect::<Vec<_>>(),
156 "package resolution matches",
157 );
158
159 if matches.is_empty() {
160 return Err(QueryError::NoMatches {
161 query: package.clone(),
162 archived_versions: Vec::new(),
163 });
164 }
165
166 Ok(matches)
167 }
168 None => Err(QueryError::NotFound {
169 query: package.clone(),
170 }),
171 }
172 }
173 PackageSource::Ident(PackageIdent::Hash(hash)) => self
174 .hash_packages
175 .get(hash)
176 .map(|x| vec![x.clone()])
177 .ok_or_else(|| QueryError::NoMatches {
178 query: package.clone(),
179 archived_versions: Vec::new(),
180 }),
181 PackageSource::Url(_) | PackageSource::Path(_) => Err(QueryError::Unsupported {
182 query: package.clone(),
183 }),
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use tempfile::TempDir;
191
192 use crate::runtime::resolver::{
193 Dependency, WebcHash,
194 inputs::{DistributionInfo, FileSystemMapping, PackageInfo},
195 };
196
197 use super::*;
198
199 const PYTHON: &[u8] = include_bytes!(concat!(
200 env!("CARGO_MANIFEST_DIR"),
201 "/../../wasmer-test-files/examples/python-0.1.0.wasmer"
202 ));
203 const COREUTILS_16: &[u8] = include_bytes!(concat!(
204 env!("CARGO_MANIFEST_DIR"),
205 "/../../wasmer-test-files/integration/webc/coreutils-1.0.16-e27dbb4f-2ef2-4b44-b46a-ddd86497c6d7.webc"
206 ));
207 const COREUTILS_11: &[u8] = include_bytes!(concat!(
208 env!("CARGO_MANIFEST_DIR"),
209 "/../../wasmer-test-files/integration/webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc"
210 ));
211 const BASH: &[u8] = include_bytes!(concat!(
212 env!("CARGO_MANIFEST_DIR"),
213 "/../../wasmer-test-files/integration/webc/bash-1.0.16-f097441a-a80b-4e0d-87d7-684918ef4bb6.webc"
214 ));
215
216 #[test]
217 fn load_a_directory_tree() {
218 let temp = TempDir::new().unwrap();
219 std::fs::write(temp.path().join("python-0.1.0.webc"), PYTHON).unwrap();
220 std::fs::write(temp.path().join("coreutils-1.0.16.webc"), COREUTILS_16).unwrap();
221 std::fs::write(temp.path().join("coreutils-1.0.11.webc"), COREUTILS_11).unwrap();
222 let nested = temp.path().join("nested");
223 std::fs::create_dir(&nested).unwrap();
224 let bash = nested.join("bash-1.0.12.webc");
225 std::fs::write(&bash, BASH).unwrap();
226
227 let source = InMemorySource::from_directory_tree(temp.path()).unwrap();
228
229 assert_eq!(
230 source
231 .named_packages
232 .keys()
233 .map(|k| k.as_str())
234 .collect::<Vec<_>>(),
235 ["python", "sharrattj/bash", "sharrattj/coreutils"]
236 );
237 assert_eq!(source.named_packages["sharrattj/coreutils"].len(), 2);
238 assert_eq!(
239 source.named_packages["sharrattj/bash"][0].summary,
240 PackageSummary {
241 pkg: PackageInfo {
242 id: PackageId::Named(
243 NamedPackageId::try_new("sharrattj/bash", "1.0.16").unwrap()
244 ),
245 dependencies: vec![Dependency {
246 alias: "coreutils".to_string(),
247 pkg: "sharrattj/coreutils@^1.0.16".parse().unwrap()
248 }],
249 commands: vec![crate::runtime::resolver::Command {
250 name: "bash".to_string(),
251 }],
252 entrypoint: Some("bash".to_string()),
253 filesystem: vec![FileSystemMapping {
254 volume_name: "atom".to_string(),
255 mount_path: "/".to_string(),
256 original_path: Some("/".to_string()),
257 dependency_name: None,
258 }],
259 },
260 dist: DistributionInfo {
261 webc: crate::runtime::resolver::utils::url_from_file_path(
262 bash.canonicalize().unwrap()
263 )
264 .unwrap(),
265 webc_sha256: WebcHash::from_bytes([
266 161, 101, 23, 194, 244, 92, 186, 213, 143, 33, 200, 128, 238, 23, 185, 174,
267 180, 195, 144, 145, 78, 17, 227, 159, 118, 64, 83, 153, 0, 205, 253, 215,
268 ]),
269 },
270 }
271 );
272 }
273}