Skip to content

Commit 3acc7ab

Browse files
committed
move fs (std) resolve functionality to resolve::fs and move resolve.rs to resolve/mod.rs
cleanup
1 parent 03c974f commit 3acc7ab

File tree

2 files changed

+281
-259
lines changed

2 files changed

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

0 commit comments

Comments
 (0)