diff options
author | Adam Joseph <adam@westernsemico.com> | 2022-10-10T18·43-0700 |
---|---|---|
committer | Adam Joseph <adam@westernsemico.com> | 2022-10-12T08·09+0000 |
commit | 32ac7d6c6d06b5191fcc828b762b247b0e27dfda (patch) | |
tree | 2ab484b94369b3a073f64b51360a57a1d20a4d80 /tvix/eval/src/nix_search_path.rs | |
parent | 04fccd89a50df7e36129493110f38c69563d941b (diff) |
refactor(tvix/eval) s/NixPath/NixSearchPath/ r/5113
Since NixString is the Rust type for nix strings, people might mistake NixPath for the Rust type of nix paths, which it is not. Let's call it NixSearchPath instead. Signed-off-by: Adam Joseph <adam@westernsemico.com> Change-Id: Ib2ea155c4b27cb90d6180a04ea7b951d86607373 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6927 Reviewed-by: kanepyork <rikingcoding@gmail.com> Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su>
Diffstat (limited to 'tvix/eval/src/nix_search_path.rs')
-rw-r--r-- | tvix/eval/src/nix_search_path.rs | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/tvix/eval/src/nix_search_path.rs b/tvix/eval/src/nix_search_path.rs new file mode 100644 index 000000000000..82a2a950998b --- /dev/null +++ b/tvix/eval/src/nix_search_path.rs @@ -0,0 +1,207 @@ +use std::convert::Infallible; +use std::io; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use crate::errors::ErrorKind; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum NixSearchPathEntry { + /// Resolve subdirectories of this path within `<...>` brackets. This + /// corresponds to bare paths within the `NIX_PATH` environment variable + /// + /// For example, with `NixSearchPathEntry::Path("/example")` and the following + /// directory structure: + /// + /// ```notrust + /// example + /// └── subdir + /// └── grandchild + /// ``` + /// + /// A Nix path literal `<subdir>` would resolve to `/example/subdir`, and a + /// Nix path literal `<subdir/grandchild>` would resolve to + /// `/example/subdir/grandchild` + Path(PathBuf), + + /// Resolve paths starting with `prefix` as subdirectories of `path`. This + /// corresponds to `prefix=path` within the `NIX_PATH` environment variable. + /// + /// For example, with `NixSearchPathEntry::Prefix { prefix: "prefix", path: + /// "/example" }` and the following directory structure: + /// + /// ```notrust + /// example + /// └── subdir + /// └── grandchild + /// ``` + /// + /// A Nix path literal `<prefix/subdir>` would resolve to `/example/subdir`, + /// and a Nix path literal `<prefix/subdir/grandchild>` would resolve to + /// `/example/subdir/grandchild` + Prefix { prefix: PathBuf, path: PathBuf }, +} + +impl NixSearchPathEntry { + fn resolve(&self, lookup_path: &Path) -> io::Result<Option<PathBuf>> { + let resolve_in = + |parent: &Path, lookup_path: &Path| match parent.join(lookup_path).canonicalize() { + Ok(path) => Ok(Some(path)), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(e), + }; + + match self { + NixSearchPathEntry::Path(p) => resolve_in(p, lookup_path), + NixSearchPathEntry::Prefix { prefix, path } => { + if let Ok(child_path) = lookup_path.strip_prefix(prefix) { + resolve_in(path, child_path) + } else { + Ok(None) + } + } + } + } +} + +impl FromStr for NixSearchPathEntry { + type Err = Infallible; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.split_once('=') { + Some((prefix, path)) => Ok(Self::Prefix { + prefix: prefix.into(), + path: path.into(), + }), + None => Ok(Self::Path(s.into())), + } + } +} + +/// Struct implementing the format and path resolution rules of the `NIX_PATH` +/// environment variable. +/// +/// This struct can be constructed by parsing a string using the [`FromStr`] +/// impl, or via [`str::parse`]. Nix `<...>` paths can then be resolved using +/// [`NixSearchPath::resolve`]. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct NixSearchPath { + entries: Vec<NixSearchPathEntry>, +} + +impl NixSearchPath { + /// Attempt to resolve the given `path` within this [`NixSearchPath`] using the + /// path resolution rules for `<...>`-style paths + #[allow(dead_code)] // TODO(grfn) + pub fn resolve<P>(&self, path: P) -> Result<PathBuf, ErrorKind> + where + P: AsRef<Path>, + { + let path = path.as_ref(); + for entry in &self.entries { + if let Some(p) = entry.resolve(path)? { + return Ok(p); + } + } + Err(ErrorKind::PathResolution(format!( + "path '{}' was not found in the Nix search path", + path.display() + ))) + } +} + +impl FromStr for NixSearchPath { + type Err = Infallible; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let entries = s + .split(':') + .map(|s| s.parse()) + .collect::<Result<Vec<_>, _>>()?; + Ok(NixSearchPath { entries }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod parse { + use super::*; + + #[test] + fn bare_paths() { + assert_eq!( + NixSearchPath::from_str("/foo/bar:/baz").unwrap(), + NixSearchPath { + entries: vec![ + NixSearchPathEntry::Path("/foo/bar".into()), + NixSearchPathEntry::Path("/baz".into()) + ], + } + ); + } + + #[test] + fn mixed_prefix_and_paths() { + assert_eq!( + NixSearchPath::from_str("nixpkgs=/my/nixpkgs:/etc/nixos").unwrap(), + NixSearchPath { + entries: vec![ + NixSearchPathEntry::Prefix { + prefix: "nixpkgs".into(), + path: "/my/nixpkgs".into() + }, + NixSearchPathEntry::Path("/etc/nixos".into()) + ], + } + ); + } + } + + mod resolve { + use std::env::current_dir; + + use path_clean::PathClean; + + use super::*; + + #[test] + fn simple_dir() { + let nix_search_path = NixSearchPath::from_str("./.").unwrap(); + let res = nix_search_path.resolve("src").unwrap(); + assert_eq!(res, current_dir().unwrap().join("src").clean()); + } + + #[test] + fn failed_resolution() { + let nix_search_path = NixSearchPath::from_str("./.").unwrap(); + let err = nix_search_path.resolve("nope").unwrap_err(); + assert!( + matches!(err, ErrorKind::PathResolution(..)), + "err = {err:?}" + ); + } + + #[test] + fn second_in_path() { + let nix_search_path = NixSearchPath::from_str("./.:/").unwrap(); + let res = nix_search_path.resolve("bin").unwrap(); + assert_eq!(res, Path::new("/bin")); + } + + #[test] + fn prefix() { + let nix_search_path = NixSearchPath::from_str("/:tvix=.").unwrap(); + let res = nix_search_path.resolve("tvix/src").unwrap(); + assert_eq!(res, current_dir().unwrap().join("src").clean()); + } + + #[test] + fn matching_prefix() { + let nix_search_path = NixSearchPath::from_str("/:tvix=.").unwrap(); + let res = nix_search_path.resolve("tvix").unwrap(); + assert_eq!(res, current_dir().unwrap().clean()); + } + } +} |