2023-03-04 01:52:47 +01:00
|
|
|
use path_clean::PathClean;
|
2022-10-10 04:57:21 +02:00
|
|
|
use std::convert::Infallible;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
use crate::errors::ErrorKind;
|
2023-03-04 01:52:47 +01:00
|
|
|
use crate::EvalIO;
|
2022-10-10 04:57:21 +02:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2022-10-10 20:43:51 +02:00
|
|
|
enum NixSearchPathEntry {
|
2022-10-10 04:57:21 +02:00
|
|
|
/// Resolve subdirectories of this path within `<...>` brackets. This
|
|
|
|
/// corresponds to bare paths within the `NIX_PATH` environment variable
|
|
|
|
///
|
2022-10-10 20:43:51 +02:00
|
|
|
/// For example, with `NixSearchPathEntry::Path("/example")` and the following
|
2022-10-10 04:57:21 +02:00
|
|
|
/// 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.
|
|
|
|
///
|
2022-10-10 20:43:51 +02:00
|
|
|
/// For example, with `NixSearchPathEntry::Prefix { prefix: "prefix", path:
|
2022-10-10 04:57:21 +02:00
|
|
|
/// "/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 },
|
|
|
|
}
|
|
|
|
|
2023-03-04 01:52:47 +01:00
|
|
|
fn canonicalise(path: PathBuf) -> Result<PathBuf, ErrorKind> {
|
|
|
|
let absolute = if path.is_absolute() {
|
|
|
|
path
|
|
|
|
} else {
|
|
|
|
// TODO(tazjin): probably panics in wasm?
|
|
|
|
std::env::current_dir()?.join(path)
|
|
|
|
}
|
|
|
|
.clean();
|
|
|
|
|
|
|
|
Ok(absolute)
|
|
|
|
}
|
|
|
|
|
2022-10-10 20:43:51 +02:00
|
|
|
impl NixSearchPathEntry {
|
2023-03-04 01:52:47 +01:00
|
|
|
/// Determine whether this path entry matches the given lookup path.
|
|
|
|
///
|
|
|
|
/// For bare paths, an entry is considered to match if a matching
|
|
|
|
/// file exists under it.
|
|
|
|
///
|
|
|
|
/// For prefixed path, an entry matches if the prefix does.
|
|
|
|
// TODO(tazjin): verify these rules in the C++ impl, seems fishy.
|
|
|
|
fn resolve(&self, io: &dyn EvalIO, lookup_path: &Path) -> Result<Option<PathBuf>, ErrorKind> {
|
|
|
|
let path = match self {
|
|
|
|
NixSearchPathEntry::Path(parent) => canonicalise(parent.join(lookup_path))?,
|
|
|
|
|
2022-10-10 20:43:51 +02:00
|
|
|
NixSearchPathEntry::Prefix { prefix, path } => {
|
2022-10-10 04:57:21 +02:00
|
|
|
if let Ok(child_path) = lookup_path.strip_prefix(prefix) {
|
2023-03-04 01:52:47 +01:00
|
|
|
canonicalise(path.join(child_path))?
|
2022-10-10 04:57:21 +02:00
|
|
|
} else {
|
2023-03-04 01:52:47 +01:00
|
|
|
return Ok(None);
|
2022-10-10 04:57:21 +02:00
|
|
|
}
|
|
|
|
}
|
2023-03-04 01:52:47 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if io.path_exists(path.clone())? {
|
|
|
|
Ok(Some(path))
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
2022-10-10 04:57:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-10 20:43:51 +02:00
|
|
|
impl FromStr for NixSearchPathEntry {
|
2022-10-10 04:57:21 +02:00
|
|
|
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
|
2022-10-10 20:43:51 +02:00
|
|
|
/// [`NixSearchPath::resolve`].
|
2022-10-10 04:57:21 +02:00
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
2022-10-10 20:43:51 +02:00
|
|
|
pub struct NixSearchPath {
|
|
|
|
entries: Vec<NixSearchPathEntry>,
|
2022-10-10 04:57:21 +02:00
|
|
|
}
|
|
|
|
|
2022-10-10 20:43:51 +02:00
|
|
|
impl NixSearchPath {
|
|
|
|
/// Attempt to resolve the given `path` within this [`NixSearchPath`] using the
|
2022-10-10 04:57:21 +02:00
|
|
|
/// path resolution rules for `<...>`-style paths
|
2023-03-04 01:52:47 +01:00
|
|
|
pub fn resolve<P>(&self, io: &dyn EvalIO, path: P) -> Result<PathBuf, ErrorKind>
|
2022-10-10 04:57:21 +02:00
|
|
|
where
|
|
|
|
P: AsRef<Path>,
|
|
|
|
{
|
|
|
|
let path = path.as_ref();
|
|
|
|
for entry in &self.entries {
|
2023-03-04 01:52:47 +01:00
|
|
|
if let Some(p) = entry.resolve(io, path)? {
|
2022-10-10 04:57:21 +02:00
|
|
|
return Ok(p);
|
|
|
|
}
|
|
|
|
}
|
2022-10-21 00:52:36 +02:00
|
|
|
Err(ErrorKind::NixPathResolution(format!(
|
2022-10-10 04:57:21 +02:00
|
|
|
"path '{}' was not found in the Nix search path",
|
|
|
|
path.display()
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-10 20:43:51 +02:00
|
|
|
impl FromStr for NixSearchPath {
|
2022-10-10 04:57:21 +02:00
|
|
|
type Err = Infallible;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
let entries = s
|
|
|
|
.split(':')
|
|
|
|
.map(|s| s.parse())
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
2022-10-10 20:43:51 +02:00
|
|
|
Ok(NixSearchPath { entries })
|
2022-10-10 04:57:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
mod parse {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn bare_paths() {
|
|
|
|
assert_eq!(
|
2022-10-10 20:43:51 +02:00
|
|
|
NixSearchPath::from_str("/foo/bar:/baz").unwrap(),
|
|
|
|
NixSearchPath {
|
2022-10-10 04:57:21 +02:00
|
|
|
entries: vec![
|
2022-10-10 20:43:51 +02:00
|
|
|
NixSearchPathEntry::Path("/foo/bar".into()),
|
|
|
|
NixSearchPathEntry::Path("/baz".into())
|
2022-10-10 04:57:21 +02:00
|
|
|
],
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn mixed_prefix_and_paths() {
|
|
|
|
assert_eq!(
|
2022-10-10 20:43:51 +02:00
|
|
|
NixSearchPath::from_str("nixpkgs=/my/nixpkgs:/etc/nixos").unwrap(),
|
|
|
|
NixSearchPath {
|
2022-10-10 04:57:21 +02:00
|
|
|
entries: vec![
|
2022-10-10 20:43:51 +02:00
|
|
|
NixSearchPathEntry::Prefix {
|
2022-10-10 04:57:21 +02:00
|
|
|
prefix: "nixpkgs".into(),
|
|
|
|
path: "/my/nixpkgs".into()
|
|
|
|
},
|
2022-10-10 20:43:51 +02:00
|
|
|
NixSearchPathEntry::Path("/etc/nixos".into())
|
2022-10-10 04:57:21 +02:00
|
|
|
],
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mod resolve {
|
2023-03-04 01:52:47 +01:00
|
|
|
use crate::StdIO;
|
2022-10-10 04:57:21 +02:00
|
|
|
use path_clean::PathClean;
|
2023-03-04 01:52:47 +01:00
|
|
|
use std::env::current_dir;
|
2022-10-10 04:57:21 +02:00
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple_dir() {
|
2022-10-10 20:43:51 +02:00
|
|
|
let nix_search_path = NixSearchPath::from_str("./.").unwrap();
|
2023-03-04 01:52:47 +01:00
|
|
|
let io = StdIO {};
|
|
|
|
let res = nix_search_path.resolve(&io, "src").unwrap();
|
2022-10-10 04:57:21 +02:00
|
|
|
assert_eq!(res, current_dir().unwrap().join("src").clean());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn failed_resolution() {
|
2022-10-10 20:43:51 +02:00
|
|
|
let nix_search_path = NixSearchPath::from_str("./.").unwrap();
|
2023-03-04 01:52:47 +01:00
|
|
|
let io = StdIO {};
|
|
|
|
let err = nix_search_path.resolve(&io, "nope").unwrap_err();
|
2022-10-10 04:57:21 +02:00
|
|
|
assert!(
|
2022-10-21 00:52:36 +02:00
|
|
|
matches!(err, ErrorKind::NixPathResolution(..)),
|
2022-10-10 04:57:21 +02:00
|
|
|
"err = {err:?}"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn second_in_path() {
|
2022-10-10 20:43:51 +02:00
|
|
|
let nix_search_path = NixSearchPath::from_str("./.:/").unwrap();
|
2023-03-04 01:52:47 +01:00
|
|
|
let io = StdIO {};
|
|
|
|
let res = nix_search_path.resolve(&io, "etc").unwrap();
|
2022-10-13 07:10:06 +02:00
|
|
|
assert_eq!(res, Path::new("/etc"));
|
2022-10-10 04:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn prefix() {
|
2022-10-10 20:43:51 +02:00
|
|
|
let nix_search_path = NixSearchPath::from_str("/:tvix=.").unwrap();
|
2023-03-04 01:52:47 +01:00
|
|
|
let io = StdIO {};
|
|
|
|
let res = nix_search_path.resolve(&io, "tvix/src").unwrap();
|
2022-10-10 04:57:21 +02:00
|
|
|
assert_eq!(res, current_dir().unwrap().join("src").clean());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn matching_prefix() {
|
2022-10-10 20:43:51 +02:00
|
|
|
let nix_search_path = NixSearchPath::from_str("/:tvix=.").unwrap();
|
2023-03-04 01:52:47 +01:00
|
|
|
let io = StdIO {};
|
|
|
|
let res = nix_search_path.resolve(&io, "tvix").unwrap();
|
2022-10-10 04:57:21 +02:00
|
|
|
assert_eq!(res, current_dir().unwrap().clean());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|