Skip to content

Commit

Permalink
Introduce boilerplate for checking Nix files
Browse files Browse the repository at this point in the history
For now without any checks, but this makes introducing ones fairly easy
  • Loading branch information
infinisil committed Jan 14, 2025
1 parent 176e35e commit 6b52b12
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 15 deletions.
7 changes: 3 additions & 4 deletions src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::{env, fs, process};

Expand Down Expand Up @@ -156,7 +157,7 @@ pub fn check_values(
nixpkgs_path: &Path,
nix_file_store: &mut NixFileStore,
package_names: &[String],
) -> validation::Result<ratchet::Nixpkgs> {
) -> validation::Result<BTreeMap<String, ratchet::Package>> {
let work_dir = tempfile::Builder::new()
.prefix("nixpkgs-vet")
.tempdir()
Expand Down Expand Up @@ -255,9 +256,7 @@ pub fn check_values(
.collect_vec()?,
);

Ok(check_result.map(|elems| ratchet::Nixpkgs {
packages: elems.into_iter().collect(),
}))
Ok(check_result.map(|elems| elems.into_iter().collect()))
}

/// Handle the evaluation result for an attribute in `pkgs/by-name`, making it a validation result.
Expand Down
77 changes: 77 additions & 0 deletions src/files.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use relative_path::RelativePath;
use relative_path::RelativePathBuf;
use std::collections::BTreeMap;
use std::path::Path;

use crate::nix_file::NixFileStore;
use crate::validation::ResultIteratorExt;
use crate::validation::Validation::Success;
use crate::{nix_file, ratchet, structure, validation};

/// Runs check on all Nix files, returning a ratchet result for each
pub fn check_files(
nixpkgs_path: &Path,
nix_file_store: &mut NixFileStore,
) -> validation::Result<BTreeMap<RelativePathBuf, ratchet::File>> {
process_nix_files(nixpkgs_path, nix_file_store, |_nix_file| {
// Noop for now, only boilerplate to make it easier to add future file-based checks
Ok(Success(ratchet::File {}))
})
}

/// Processes all Nix files in a Nixpkgs directory according to a given function `f`, collecting the
/// results into a mapping from each file to a ratchet value.
fn process_nix_files(
nixpkgs_path: &Path,
nix_file_store: &mut NixFileStore,
f: impl Fn(&nix_file::NixFile) -> validation::Result<ratchet::File>,
) -> validation::Result<BTreeMap<RelativePathBuf, ratchet::File>> {
// Get all Nix files
let files = {
let mut files = vec![];
collect_nix_files(nixpkgs_path, &RelativePathBuf::new(), &mut files)?;
files
};

let results = files
.into_iter()
.map(|path| {
// Get the (optionally-cached) parsed Nix file
let nix_file = nix_file_store.get(&path.to_path(nixpkgs_path))?;
let result = f(nix_file)?;
let val = result.map(|ratchet| (path, ratchet));
Ok::<_, anyhow::Error>(val)
})
.collect_vec()?;

Ok(validation::sequence(results).map(|entries| {
// Convert the Vec to a BTreeMap
entries.into_iter().collect()
}))
}

/// Recursively collects all Nix files in the relative `dir` within `base`
/// into the `files` `Vec`.
fn collect_nix_files(
base: &Path,
dir: &RelativePath,
files: &mut Vec<RelativePathBuf>,
) -> anyhow::Result<()> {
for entry in structure::read_dir_sorted(&dir.to_path(base))? {
let mut relative_path = dir.to_relative_path_buf();
relative_path.push(entry.file_name().to_string_lossy().into_owned());

let absolute_path = entry.path();

// We'll get to every file based on directory recursion, no need to follow symlinks.
if absolute_path.is_symlink() {
continue;
}
if absolute_path.is_dir() {
collect_nix_files(base, &relative_path, files)?
} else if absolute_path.extension().is_some_and(|x| x == "nix") {
files.push(relative_path)
}
}
Ok(())
}
34 changes: 23 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// #![allow(clippy::missing_const_for_fn)]

mod eval;
mod files;
mod location;
mod nix_file;
mod problem;
Expand All @@ -21,6 +22,7 @@ mod validation;

use anyhow::Context as _;
use clap::Parser;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::{panic, thread};
Expand Down Expand Up @@ -113,20 +115,30 @@ fn check_nixpkgs(nixpkgs_path: &Path) -> validation::Result<ratchet::Nixpkgs> {
)
})?;

if !nixpkgs_path.join(structure::BASE_SUBPATH).exists() {
// No pkgs/by-name directory, always valid
return Ok(Success(ratchet::Nixpkgs::default()));
}

let mut nix_file_store = NixFileStore::default();
let structure = check_structure(&nixpkgs_path, &mut nix_file_store)?;

// Only if we could successfully parse the structure, we do the evaluation checks
let result = structure.result_map(|package_names| {
eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names.as_slice())
})?;
let package_result = {
if !nixpkgs_path.join(structure::BASE_SUBPATH).exists() {
// No pkgs/by-name directory, always valid
Success(BTreeMap::new())
} else {
let structure = check_structure(&nixpkgs_path, &mut nix_file_store)?;

// Only if we could successfully parse the structure, we do the evaluation checks
structure.result_map(|package_names| {
eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names.as_slice())
})?
}
};

let file_result = files::check_files(&nixpkgs_path, &mut nix_file_store)?;

Ok(result)
Ok(
package_result.and(file_result, |packages, files| ratchet::Nixpkgs {
packages,
files,
}),
)
}

#[cfg(test)]
Expand Down
18 changes: 18 additions & 0 deletions src/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! Each type has a `compare` method that validates the ratchet checks for that item.
use relative_path::RelativePath;
use std::collections::BTreeMap;

use relative_path::RelativePathBuf;
Expand All @@ -15,6 +16,7 @@ use crate::validation::{self, Validation, Validation::Success};
pub struct Nixpkgs {
/// The ratchet values for all packages
pub packages: BTreeMap<String, Package>,
pub files: BTreeMap<RelativePathBuf, File>,
}

impl Nixpkgs {
Expand All @@ -27,6 +29,9 @@ impl Nixpkgs {
.into_iter()
.map(|(name, pkg)| Package::compare(&name, from.packages.get(&name), &pkg)),
)
.and_(validation::sequence_(to.files.into_iter().map(
|(name, file)| File::compare(&name, from.files.get(&name), &file),
)))
}
}

Expand Down Expand Up @@ -57,6 +62,19 @@ impl Package {
}
}

pub struct File {}

impl File {
/// Validates the ratchet checks for a top-level package
pub fn compare(
_name: &RelativePath,
_optional_from: Option<&Self>,
_to: &Self,
) -> Validation<()> {
Success(())
}
}

/// The ratchet state of a generic ratchet check.
pub enum RatchetState<Ratchet: ToProblem> {
/// The ratchet is loose. It can be tightened more. In other words, this is the legacy state
Expand Down

0 comments on commit 6b52b12

Please sign in to comment.