Skip to content

Commit 11fb3da

Browse files
committed
Introduce boilerplate for checking Nix files
For now without any checks, but this makes introducing ones fairly easy
1 parent 176e35e commit 11fb3da

File tree

4 files changed

+123
-15
lines changed

4 files changed

+123
-15
lines changed

src/eval.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::BTreeMap;
12
use std::path::{Path, PathBuf};
23
use std::{env, fs, process};
34

@@ -152,11 +153,13 @@ fn mutate_nix_instatiate_arguments_based_on_cfg(
152153
/// Check that the Nixpkgs attribute values corresponding to the packages in `pkgs/by-name` are of
153154
/// the form `callPackage <package_file> { ... }`. See the `./eval.nix` file for how this is
154155
/// achieved on the Nix side.
156+
///
157+
/// The validation result is a map from package names to a package ratchet state.
155158
pub fn check_values(
156159
nixpkgs_path: &Path,
157160
nix_file_store: &mut NixFileStore,
158161
package_names: &[String],
159-
) -> validation::Result<ratchet::Nixpkgs> {
162+
) -> validation::Result<BTreeMap<String, ratchet::Package>> {
160163
let work_dir = tempfile::Builder::new()
161164
.prefix("nixpkgs-vet")
162165
.tempdir()
@@ -255,9 +258,7 @@ pub fn check_values(
255258
.collect_vec()?,
256259
);
257260

258-
Ok(check_result.map(|elems| ratchet::Nixpkgs {
259-
packages: elems.into_iter().collect(),
260-
}))
261+
Ok(check_result.map(|elems| elems.into_iter().collect()))
261262
}
262263

263264
/// Handle the evaluation result for an attribute in `pkgs/by-name`, making it a validation result.

src/files.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use relative_path::RelativePath;
2+
use relative_path::RelativePathBuf;
3+
use std::collections::BTreeMap;
4+
use std::path::Path;
5+
6+
use crate::nix_file::NixFileStore;
7+
use crate::validation::ResultIteratorExt;
8+
use crate::validation::Validation::Success;
9+
use crate::{nix_file, ratchet, structure, validation};
10+
11+
/// Runs check on all Nix files, returning a ratchet result for each
12+
pub fn check_files(
13+
nixpkgs_path: &Path,
14+
nix_file_store: &mut NixFileStore,
15+
) -> validation::Result<BTreeMap<RelativePathBuf, ratchet::File>> {
16+
process_nix_files(nixpkgs_path, nix_file_store, |_nix_file| {
17+
// Noop for now, only boilerplate to make it easier to add future file-based checks
18+
Ok(Success(ratchet::File {}))
19+
})
20+
}
21+
22+
/// Processes all Nix files in a Nixpkgs directory according to a given function `f`, collecting the
23+
/// results into a mapping from each file to a ratchet value.
24+
fn process_nix_files(
25+
nixpkgs_path: &Path,
26+
nix_file_store: &mut NixFileStore,
27+
f: impl Fn(&nix_file::NixFile) -> validation::Result<ratchet::File>,
28+
) -> validation::Result<BTreeMap<RelativePathBuf, ratchet::File>> {
29+
// Get all Nix files
30+
let files = {
31+
let mut files = vec![];
32+
collect_nix_files(nixpkgs_path, &RelativePathBuf::new(), &mut files)?;
33+
files
34+
};
35+
36+
let results = files
37+
.into_iter()
38+
.map(|path| {
39+
// Get the (optionally-cached) parsed Nix file
40+
let nix_file = nix_file_store.get(&path.to_path(nixpkgs_path))?;
41+
let result = f(nix_file)?;
42+
let val = result.map(|ratchet| (path, ratchet));
43+
Ok::<_, anyhow::Error>(val)
44+
})
45+
.collect_vec()?;
46+
47+
Ok(validation::sequence(results).map(|entries| {
48+
// Convert the Vec to a BTreeMap
49+
entries.into_iter().collect()
50+
}))
51+
}
52+
53+
/// Recursively collects all Nix files in the relative `dir` within `base`
54+
/// into the `files` `Vec`.
55+
fn collect_nix_files(
56+
base: &Path,
57+
dir: &RelativePath,
58+
files: &mut Vec<RelativePathBuf>,
59+
) -> anyhow::Result<()> {
60+
for entry in structure::read_dir_sorted(&dir.to_path(base))? {
61+
let mut relative_path = dir.to_relative_path_buf();
62+
relative_path.push(entry.file_name().to_string_lossy().into_owned());
63+
64+
let absolute_path = entry.path();
65+
66+
// We'll get to every file based on directory recursion, no need to follow symlinks.
67+
if absolute_path.is_symlink() {
68+
continue;
69+
}
70+
if absolute_path.is_dir() {
71+
collect_nix_files(base, &relative_path, files)?
72+
} else if absolute_path.extension().is_some_and(|x| x == "nix") {
73+
files.push(relative_path)
74+
}
75+
}
76+
Ok(())
77+
}

src/main.rs

+23-11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// #![allow(clippy::missing_const_for_fn)]
1111

1212
mod eval;
13+
mod files;
1314
mod location;
1415
mod nix_file;
1516
mod problem;
@@ -21,6 +22,7 @@ mod validation;
2122

2223
use anyhow::Context as _;
2324
use clap::Parser;
25+
use std::collections::BTreeMap;
2426
use std::path::{Path, PathBuf};
2527
use std::process::ExitCode;
2628
use std::{panic, thread};
@@ -113,20 +115,30 @@ fn check_nixpkgs(nixpkgs_path: &Path) -> validation::Result<ratchet::Nixpkgs> {
113115
)
114116
})?;
115117

116-
if !nixpkgs_path.join(structure::BASE_SUBPATH).exists() {
117-
// No pkgs/by-name directory, always valid
118-
return Ok(Success(ratchet::Nixpkgs::default()));
119-
}
120-
121118
let mut nix_file_store = NixFileStore::default();
122-
let structure = check_structure(&nixpkgs_path, &mut nix_file_store)?;
123119

124-
// Only if we could successfully parse the structure, we do the evaluation checks
125-
let result = structure.result_map(|package_names| {
126-
eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names.as_slice())
127-
})?;
120+
let package_result = {
121+
if !nixpkgs_path.join(structure::BASE_SUBPATH).exists() {
122+
// No pkgs/by-name directory, always valid
123+
Success(BTreeMap::new())
124+
} else {
125+
let structure = check_structure(&nixpkgs_path, &mut nix_file_store)?;
126+
127+
// Only if we could successfully parse the structure, we do the evaluation checks
128+
structure.result_map(|package_names| {
129+
eval::check_values(&nixpkgs_path, &mut nix_file_store, package_names.as_slice())
130+
})?
131+
}
132+
};
133+
134+
let file_result = files::check_files(&nixpkgs_path, &mut nix_file_store)?;
128135

129-
Ok(result)
136+
Ok(
137+
package_result.and(file_result, |packages, files| ratchet::Nixpkgs {
138+
packages,
139+
files,
140+
}),
141+
)
130142
}
131143

132144
#[cfg(test)]

src/ratchet.rs

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! Each type has a `compare` method that validates the ratchet checks for that item.
44
5+
use relative_path::RelativePath;
56
use std::collections::BTreeMap;
67

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

2022
impl Nixpkgs {
@@ -27,6 +29,9 @@ impl Nixpkgs {
2729
.into_iter()
2830
.map(|(name, pkg)| Package::compare(&name, from.packages.get(&name), &pkg)),
2931
)
32+
.and_(validation::sequence_(to.files.into_iter().map(
33+
|(name, file)| File::compare(&name, from.files.get(&name), &file),
34+
)))
3035
}
3136
}
3237

@@ -57,6 +62,19 @@ impl Package {
5762
}
5863
}
5964

65+
pub struct File {}
66+
67+
impl File {
68+
/// Validates the ratchet checks for a top-level package
69+
pub fn compare(
70+
_name: &RelativePath,
71+
_optional_from: Option<&Self>,
72+
_to: &Self,
73+
) -> Validation<()> {
74+
Success(())
75+
}
76+
}
77+
6078
/// The ratchet state of a generic ratchet check.
6179
pub enum RatchetState<Ratchet: ToProblem> {
6280
/// The ratchet is loose. It can be tightened more. In other words, this is the legacy state

0 commit comments

Comments
 (0)