/src/wasm-tools/crates/wit-parser/src/resolve/fs.rs
Line | Count | Source |
1 | | //! Filesystem operations for [`Resolve`]. |
2 | | |
3 | | use alloc::format; |
4 | | use std::path::Path; |
5 | | use std::vec::Vec; |
6 | | |
7 | | use anyhow::{Context, Result, bail}; |
8 | | |
9 | | use super::{PackageSources, Resolve}; |
10 | | use crate::{SourceMap, UnresolvedPackageGroup}; |
11 | | |
12 | | /// All the sources used during resolving a directory or path. |
13 | | #[derive(Clone, Debug)] |
14 | | pub struct PackageSourceMap { |
15 | | inner: PackageSources, |
16 | | } |
17 | | |
18 | | impl PackageSourceMap { |
19 | 0 | fn from_single_source(package_id: super::PackageId, source: &Path) -> Result<Self> { |
20 | 0 | let path_str = source |
21 | 0 | .to_str() |
22 | 0 | .ok_or_else(|| anyhow::anyhow!("path is not valid utf-8: {source:?}"))?; |
23 | 0 | Ok(Self { |
24 | 0 | inner: PackageSources::from_single_source(package_id, path_str), |
25 | 0 | }) |
26 | 0 | } |
27 | | |
28 | 0 | fn from_inner(inner: PackageSources) -> Self { |
29 | 0 | Self { inner } |
30 | 0 | } |
31 | | |
32 | | /// All unique source paths. |
33 | 0 | pub fn paths(&self) -> impl Iterator<Item = &Path> { |
34 | 0 | self.inner.source_names().map(Path::new) |
35 | 0 | } |
36 | | |
37 | | /// Source paths for package |
38 | 0 | pub fn package_paths(&self, id: super::PackageId) -> Option<impl Iterator<Item = &Path>> { |
39 | 0 | self.inner |
40 | 0 | .package_source_names(id) |
41 | 0 | .map(|iter| iter.map(Path::new)) |
42 | 0 | } |
43 | | } |
44 | | |
45 | | enum ParsedFile { |
46 | | #[cfg(feature = "decoding")] |
47 | | Package(super::PackageId), |
48 | | Unresolved(UnresolvedPackageGroup), |
49 | | } |
50 | | |
51 | | impl Resolve { |
52 | | /// Parse WIT packages from the input `path`. |
53 | | /// |
54 | | /// The input `path` can be one of: |
55 | | /// |
56 | | /// * A directory containing a WIT package with an optional `deps` directory |
57 | | /// for local dependencies. In this case `deps` is parsed first and then |
58 | | /// the parent `path` is parsed and returned. |
59 | | /// * A single standalone WIT file. |
60 | | /// * A wasm-encoded WIT package as a single file in either the text or |
61 | | /// binary format. |
62 | | /// |
63 | | /// More information can also be found at [`Resolve::push_dir`] and |
64 | | /// [`Resolve::push_file`]. |
65 | 0 | pub fn push_path( |
66 | 0 | &mut self, |
67 | 0 | path: impl AsRef<Path>, |
68 | 0 | ) -> Result<(super::PackageId, PackageSourceMap)> { |
69 | 0 | self._push_path(path.as_ref()) |
70 | 0 | } |
71 | | |
72 | 0 | fn _push_path(&mut self, path: &Path) -> Result<(super::PackageId, PackageSourceMap)> { |
73 | 0 | if path.is_dir() { |
74 | 0 | self.push_dir(path).with_context(|| { |
75 | 0 | format!( |
76 | | "failed to resolve directory while parsing WIT for path [{}]", |
77 | 0 | path.display() |
78 | | ) |
79 | 0 | }) |
80 | | } else { |
81 | 0 | let id = self.push_file(path)?; |
82 | 0 | Ok((id, PackageSourceMap::from_single_source(id, path)?)) |
83 | | } |
84 | 0 | } |
85 | | |
86 | | /// Parses the filesystem directory at `path` as a WIT package and returns |
87 | | /// a fully resolved [`super::PackageId`] list as a result. |
88 | | /// |
89 | | /// All `*.wit` files in `path` are parsed as a single package and then |
90 | | /// inserted into this `Resolve`. The `path` specified may have a `deps` |
91 | | /// subdirectory which is probed automatically for any other WIT |
92 | | /// dependencies. |
93 | | /// |
94 | | /// The `deps` folder may contain: |
95 | | /// |
96 | | /// * `$path/deps/my-package/*.wit` - a directory that may contain multiple |
97 | | /// WIT files. The directory is parsed as a single package and then |
98 | | /// inserted into this [`Resolve`]. Note that it cannot recursively |
99 | | /// contain a `deps` directory. |
100 | | /// * `$path/deps/my-package.wit` - a single-file WIT package. This is |
101 | | /// parsed with [`Resolve::push_file`] and then added to `self` for |
102 | | /// name resolution. |
103 | | /// * `$path/deps/my-package.{wasm,wat}` - a wasm-encoded WIT package either |
104 | | /// in the text for binary format. |
105 | | /// |
106 | | /// In all cases entries in the `deps` folder are added to `self` first |
107 | | /// before adding files found in `path` itself. All WIT packages found are |
108 | | /// candidates for name-based resolution that other packages may use. |
109 | | /// |
110 | | /// This function returns a tuple of two values. The first value is a |
111 | | /// [`super::PackageId`], which represents the main WIT package found within |
112 | | /// `path`. This argument is useful for passing to [`Resolve::select_world`] |
113 | | /// for choosing something to bindgen with. |
114 | | /// |
115 | | /// The second value returned is a [`PackageSourceMap`], which contains all the sources |
116 | | /// that were parsed during resolving. This can be useful for: |
117 | | /// * build systems that want to rebuild bindings whenever one of the files changed |
118 | | /// * or other tools, which want to identify the sources for the resolved packages |
119 | 0 | pub fn push_dir( |
120 | 0 | &mut self, |
121 | 0 | path: impl AsRef<Path>, |
122 | 0 | ) -> Result<(super::PackageId, PackageSourceMap)> { |
123 | 0 | self._push_dir(path.as_ref()) |
124 | 0 | } |
125 | | |
126 | 0 | fn _push_dir(&mut self, path: &Path) -> Result<(super::PackageId, PackageSourceMap)> { |
127 | 0 | let top_pkg = self |
128 | 0 | .parse_dir(path) |
129 | 0 | .with_context(|| format!("failed to parse package: {}", path.display()))?; |
130 | 0 | let deps = path.join("deps"); |
131 | 0 | let deps = self |
132 | 0 | .parse_deps_dir(&deps) |
133 | 0 | .with_context(|| format!("failed to parse dependency directory: {}", deps.display()))?; |
134 | | |
135 | 0 | let (pkg_id, inner) = self.sort_unresolved_packages(top_pkg, deps)?; |
136 | 0 | Ok((pkg_id, PackageSourceMap::from_inner(inner))) |
137 | 0 | } |
138 | | |
139 | | /// Reads `*.wit` files from `path` into a [`SourceMap`] and parses them as |
140 | | /// an [`UnresolvedPackageGroup`]. See [`Resolve::parse_source_map`] for the |
141 | | /// failure-path semantics. |
142 | 0 | fn parse_dir(&mut self, path: &Path) -> Result<UnresolvedPackageGroup> { |
143 | 0 | let mut map = SourceMap::default(); |
144 | 0 | map.push_dir(path)?; |
145 | 0 | self.parse_source_map(map) |
146 | 0 | } |
147 | | |
148 | 0 | fn parse_deps_dir(&mut self, path: &Path) -> Result<Vec<UnresolvedPackageGroup>> { |
149 | 0 | let mut ret = Vec::new(); |
150 | 0 | if !path.exists() { |
151 | 0 | return Ok(ret); |
152 | 0 | } |
153 | 0 | let mut entries = path |
154 | 0 | .read_dir() |
155 | 0 | .and_then(|i| i.collect::<std::io::Result<Vec<_>>>()) |
156 | 0 | .context("failed to read directory")?; |
157 | 0 | entries.sort_by_key(|e| e.file_name()); |
158 | 0 | for dep in entries { |
159 | 0 | let path = dep.path(); |
160 | 0 | let pkg = if dep.file_type()?.is_dir() || path.metadata()?.is_dir() { |
161 | | // If this entry is a directory or a symlink point to a |
162 | | // directory then always parse it as an `UnresolvedPackage` |
163 | | // since it's intentional to not support recursive `deps` |
164 | | // directories. |
165 | 0 | self.parse_dir(&path) |
166 | 0 | .with_context(|| format!("failed to parse package: {}", path.display()))? |
167 | | } else { |
168 | | // If this entry is a file then we may want to ignore it but |
169 | | // this may also be a standalone WIT file or a `*.wasm` or |
170 | | // `*.wat` encoded package. |
171 | 0 | let filename = dep.file_name(); |
172 | 0 | match Path::new(&filename).extension().and_then(|s| s.to_str()) { |
173 | 0 | Some("wit") | Some("wat") | Some("wasm") => match self._push_file(&path)? { |
174 | | #[cfg(feature = "decoding")] |
175 | 0 | ParsedFile::Package(_) => continue, |
176 | 0 | ParsedFile::Unresolved(pkg) => pkg, |
177 | | }, |
178 | | |
179 | | // Other files in deps dir are ignored for now to avoid |
180 | | // accidentally including things like `.DS_Store` files in |
181 | | // the call below to `parse_dir`. |
182 | 0 | _ => continue, |
183 | | } |
184 | | }; |
185 | 0 | ret.push(pkg); |
186 | | } |
187 | 0 | Ok(ret) |
188 | 0 | } |
189 | | |
190 | | /// Parses the contents of `path` from the filesystem and pushes the result |
191 | | /// into this `Resolve`. |
192 | | /// |
193 | | /// The `path` referenced here can be one of: |
194 | | /// |
195 | | /// * A WIT file. Note that in this case this single WIT file will be the |
196 | | /// entire package and any dependencies it has must already be in `self`. |
197 | | /// * A WIT package encoded as WebAssembly, either in text or binary form. |
198 | | /// In this the package and all of its dependencies are automatically |
199 | | /// inserted into `self`. |
200 | | /// |
201 | | /// In both situations the `PackageId`s of the resulting resolved packages |
202 | | /// are returned from this method. The return value is mostly useful in |
203 | | /// conjunction with [`Resolve::select_world`]. |
204 | 0 | pub fn push_file(&mut self, path: impl AsRef<Path>) -> Result<super::PackageId> { |
205 | 0 | match self._push_file(path.as_ref())? { |
206 | | #[cfg(feature = "decoding")] |
207 | 0 | ParsedFile::Package(id) => Ok(id), |
208 | 0 | ParsedFile::Unresolved(pkg) => Ok(self.push_group(pkg)?), |
209 | | } |
210 | 0 | } |
211 | | |
212 | 0 | fn _push_file(&mut self, path: &Path) -> Result<ParsedFile> { |
213 | 0 | let contents = std::fs::read(path) |
214 | 0 | .with_context(|| format!("failed to read path for WIT [{}]", path.display()))?; |
215 | | |
216 | | // If decoding is enabled at compile time then try to see if this is a |
217 | | // wasm file. |
218 | | #[cfg(feature = "decoding")] |
219 | | { |
220 | | use crate::decoding::{DecodedWasm, decode}; |
221 | | |
222 | | #[cfg(feature = "wat")] |
223 | | let is_wasm = wat::Detect::from_bytes(&contents).is_wasm(); |
224 | | #[cfg(not(feature = "wat"))] |
225 | 0 | let is_wasm = wasmparser::Parser::is_component(&contents); |
226 | | |
227 | 0 | if is_wasm { |
228 | | #[cfg(feature = "wat")] |
229 | | let contents = wat::parse_bytes(&contents).map_err(|mut e| { |
230 | | e.set_path(path); |
231 | | e |
232 | | })?; |
233 | | |
234 | 0 | match decode(&contents)? { |
235 | | DecodedWasm::Component(..) => { |
236 | 0 | bail!("found an actual component instead of an encoded WIT package in wasm") |
237 | | } |
238 | 0 | DecodedWasm::WitPackage(resolve, pkg) => { |
239 | 0 | let remap = self.merge(resolve)?; |
240 | 0 | return Ok(ParsedFile::Package(remap.packages[pkg.index()])); |
241 | | } |
242 | | } |
243 | 0 | } |
244 | | } |
245 | | |
246 | | // If this wasn't a wasm file then assume it's a WIT file. |
247 | 0 | let text = match core::str::from_utf8(&contents) { |
248 | 0 | Ok(s) => s, |
249 | 0 | Err(_) => bail!("input file is not valid utf-8 [{}]", path.display()), |
250 | | }; |
251 | 0 | let mut map = SourceMap::default(); |
252 | 0 | map.push(path, text); |
253 | 0 | Ok(ParsedFile::Unresolved(self.parse_source_map(map)?)) |
254 | 0 | } |
255 | | |
256 | | /// Parses `contents` as a WIT package and pushes it into this `Resolve`. |
257 | | /// |
258 | | /// The `path` provided is used for error messages but otherwise is not |
259 | | /// read. This method does not touch the filesystem. The `contents` provided |
260 | | /// are the contents of a WIT package. |
261 | 3.86k | pub fn push_str(&mut self, path: impl AsRef<Path>, contents: &str) -> Result<super::PackageId> { |
262 | 3.86k | let path = path |
263 | 3.86k | .as_ref() |
264 | 3.86k | .to_str() |
265 | 3.86k | .ok_or_else(|| anyhow::anyhow!("path is not valid utf-8: {:?}", path.as_ref()))?;Unexecuted instantiation: <wit_parser::resolve::Resolve>::push_str::<&alloc::string::String>::{closure#0}Unexecuted instantiation: <wit_parser::resolve::Resolve>::push_str::<_>::{closure#0} |
266 | 3.86k | self.push_source(path, contents) |
267 | 3.86k | } <wit_parser::resolve::Resolve>::push_str::<&alloc::string::String> Line | Count | Source | 261 | 3.86k | pub fn push_str(&mut self, path: impl AsRef<Path>, contents: &str) -> Result<super::PackageId> { | 262 | 3.86k | let path = path | 263 | 3.86k | .as_ref() | 264 | 3.86k | .to_str() | 265 | 3.86k | .ok_or_else(|| anyhow::anyhow!("path is not valid utf-8: {:?}", path.as_ref()))?; | 266 | 3.86k | self.push_source(path, contents) | 267 | 3.86k | } |
Unexecuted instantiation: <wit_parser::resolve::Resolve>::push_str::<_> |
268 | | } |