nixpkgs/pkgs/test/nixpkgs-check-by-name/src/references.rs
Silvan Mosberger 5981aff212 tests.nixpkgs-check-by-name: Use RelativePath for relative paths
Makes the code easier to understand and less error-prone
2024-03-01 23:07:34 +01:00

177 lines
6.6 KiB
Rust

use crate::nixpkgs_problem::NixpkgsProblem;
use crate::utils;
use crate::validation::{self, ResultIteratorExt, Validation::Success};
use crate::NixFileStore;
use relative_path::RelativePath;
use anyhow::Context;
use rowan::ast::AstNode;
use std::ffi::OsStr;
use std::path::Path;
/// Check that every package directory in pkgs/by-name doesn't link to outside that directory.
/// Both symlinks and Nix path expressions are checked.
pub fn check_references(
nix_file_store: &mut NixFileStore,
relative_package_dir: &RelativePath,
absolute_package_dir: &Path,
) -> validation::Result<()> {
// The first subpath to check is the package directory itself, which we can represent as an
// empty path, since the absolute package directory gets prepended to this.
// We don't use `./.` to keep the error messages cleaner
// (there's no canonicalisation going on underneath)
let subpath = RelativePath::new("");
check_path(
nix_file_store,
relative_package_dir,
absolute_package_dir,
subpath,
)
.with_context(|| {
format!(
"While checking the references in package directory {}",
relative_package_dir
)
})
}
/// Checks for a specific path to not have references outside
///
/// The subpath is the relative path within the package directory we're currently checking.
/// A relative path so that the error messages don't get absolute paths (which are messy in CI).
/// The absolute package directory gets prepended before doing anything with it though.
fn check_path(
nix_file_store: &mut NixFileStore,
relative_package_dir: &RelativePath,
absolute_package_dir: &Path,
subpath: &RelativePath,
) -> validation::Result<()> {
let path = subpath.to_path(absolute_package_dir);
Ok(if path.is_symlink() {
// Check whether the symlink resolves to outside the package directory
match path.canonicalize() {
Ok(target) => {
// No need to handle the case of it being inside the directory, since we scan through the
// entire directory recursively anyways
if let Err(_prefix_error) = target.strip_prefix(absolute_package_dir) {
NixpkgsProblem::OutsideSymlink {
relative_package_dir: relative_package_dir.to_owned(),
subpath: subpath.to_owned(),
}
.into()
} else {
Success(())
}
}
Err(io_error) => NixpkgsProblem::UnresolvableSymlink {
relative_package_dir: relative_package_dir.to_owned(),
subpath: subpath.to_owned(),
io_error: io_error.to_string(),
}
.into(),
}
} else if path.is_dir() {
// Recursively check each entry
validation::sequence_(
utils::read_dir_sorted(&path)?
.into_iter()
.map(|entry| {
check_path(
nix_file_store,
relative_package_dir,
absolute_package_dir,
// TODO: The relative_path crate doesn't seem to support OsStr
&subpath.join(entry.file_name().to_string_lossy().to_string()),
)
})
.collect_vec()
.with_context(|| format!("Error while recursing into {}", subpath))?,
)
} else if path.is_file() {
// Only check Nix files
if let Some(ext) = path.extension() {
if ext == OsStr::new("nix") {
check_nix_file(
nix_file_store,
relative_package_dir,
absolute_package_dir,
subpath,
)
.with_context(|| format!("Error while checking Nix file {}", subpath))?
} else {
Success(())
}
} else {
Success(())
}
} else {
// This should never happen, git doesn't support other file types
anyhow::bail!("Unsupported file type for path {}", subpath);
})
}
/// Check whether a nix file contains path expression references pointing outside the package
/// directory
fn check_nix_file(
nix_file_store: &mut NixFileStore,
relative_package_dir: &RelativePath,
absolute_package_dir: &Path,
subpath: &RelativePath,
) -> validation::Result<()> {
let path = subpath.to_path(absolute_package_dir);
let nix_file = nix_file_store.get(&path)?;
Ok(validation::sequence_(
nix_file.syntax_root.syntax().descendants().map(|node| {
let text = node.text().to_string();
let line = nix_file.line_index.line(node.text_range().start().into());
// We're only interested in Path expressions
let Some(path) = rnix::ast::Path::cast(node) else {
return Success(());
};
use crate::nix_file::ResolvedPath;
match nix_file.static_resolve_path(path, absolute_package_dir) {
ResolvedPath::Interpolated => NixpkgsProblem::PathInterpolation {
relative_package_dir: relative_package_dir.to_owned(),
subpath: subpath.to_owned(),
line,
text,
}
.into(),
ResolvedPath::SearchPath => NixpkgsProblem::SearchPath {
relative_package_dir: relative_package_dir.to_owned(),
subpath: subpath.to_owned(),
line,
text,
}
.into(),
ResolvedPath::Outside => NixpkgsProblem::OutsidePathReference {
relative_package_dir: relative_package_dir.to_owned(),
subpath: subpath.to_owned(),
line,
text,
}
.into(),
ResolvedPath::Unresolvable(e) => NixpkgsProblem::UnresolvablePathReference {
relative_package_dir: relative_package_dir.to_owned(),
subpath: subpath.to_owned(),
line,
text,
io_error: e.to_string(),
}
.into(),
ResolvedPath::Within(..) => {
// No need to handle the case of it being inside the directory, since we scan through the
// entire directory recursively anyways
Success(())
}
}
}),
))
}