Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
37f8f66
tests.nixpkgs-check-by-name: Intermediate error type refactoring prep
infinisil Oct 19, 2023
ed56d74
tests.nixpkgs-check-by-name: Intermediate path reference errors
infinisil Oct 19, 2023
a755aa7
tests.nixpkgs-check-by-name: Intermediate SearchPath error
infinisil Oct 19, 2023
96f6a35
tests.nixpkgs-check-by-name: Intermediate PathInterpolation error
infinisil Oct 19, 2023
9a3abc4
tests.nixpkgs-check-by-name: Intermediate CouldNotParseNix error
infinisil Oct 19, 2023
4897b63
tests.nixpkgs-check-by-name: Intermediate Symlink errors
infinisil Oct 19, 2023
9a0ef88
tests.nixpkgs-check-by-name: Intermediate NonDerivation error
infinisil Oct 19, 2023
b688da8
tests.nixpkgs-check-by-name: Intermediate WrongCallPackage error
infinisil Oct 19, 2023
4f17b93
tests.nixpkgs-check-by-name: Intermediate UndefinedAttr error
infinisil Oct 19, 2023
e3979d1
tests.nixpkgs-check-by-name: Intermediate PackageNixDir error
infinisil Oct 19, 2023
64f5eb6
tests.nixpkgs-check-by-name: Intermediate PackageNixNonExistent error
infinisil Oct 19, 2023
b011d53
tests.nixpkgs-check-by-name: Intermediate IncorrectShard error
infinisil Oct 19, 2023
e7d9cc9
tests.nixpkgs-check-by-name: Intermediate InvalidPackageName error
infinisil Oct 19, 2023
935f822
tests.nixpkgs-check-by-name: Intermediate CaseSensitiveDuplicate error
infinisil Oct 19, 2023
143e267
tests.nixpkgs-check-by-name: Intermediate PackageNonDir error
infinisil Oct 19, 2023
b7ace01
tests.nixpkgs-check-by-name: Intermediate InvalidShardName error
infinisil Oct 19, 2023
571eaed
tests.nixpkgs-check-by-name: Intermediate ShardNonDir error
infinisil Oct 19, 2023
bb89ca7
tests.nixpkgs-check-by-name: Refactor
infinisil Oct 19, 2023
83b8875
tests.nixpkgs-check-by-name: Support for combining check results
infinisil Oct 19, 2023
0475238
tests.nixpkgs-check-by-name: Make structural check a global function
infinisil Oct 19, 2023
d65f3dd
tests.nixpkgs-check-by-name: Make reference check part of structural …
infinisil Oct 20, 2023
e58bc75
tests.nixpkgs-check-by-name: Remove Nixpkgs struct
infinisil Oct 20, 2023
3d60440
tests.nixpkgs-check-by-name: Remove error writer
infinisil Oct 20, 2023
eac0b69
tests.nixpkgs-check-by-name: Redesign and document check_result funct…
infinisil Oct 23, 2023
8be41ac
tests.nixpkgs-check-by-name: Separate file for all problems
infinisil Oct 23, 2023
03c58ad
tests.nixpkgs-check-by-name: Minor doc updates
infinisil Oct 23, 2023
82e708c
tests.nixpkgs-check-by-name: Custom Validation type and improvements
infinisil Oct 24, 2023
7753969
tests.nixpkgs-check-by-name: Remove PackageContext helper
infinisil Oct 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pkgs/test/nixpkgs-check-by-name/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkgs/test/nixpkgs-check-by-name/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ serde = { version = "1.0.185", features = ["derive"] }
anyhow = "1.0"
lazy_static = "1.4.0"
colored = "2.0.4"
itertools = "0.11.0"

[dev-dependencies]
temp-env = "0.3.5"
4 changes: 2 additions & 2 deletions pkgs/test/nixpkgs-check-by-name/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Nixpkgs pkgs/by-name checker

This directory implements a program to check the [validity](#validity-checks) of the `pkgs/by-name` Nixpkgs directory once introduced.
This directory implements a program to check the [validity](#validity-checks) of the `pkgs/by-name` Nixpkgs directory.
It is being used by [this GitHub Actions workflow](../../../.github/workflows/check-by-name.yml).
This is part of the implementation of [RFC 140](https://github.com/NixOS/rfcs/pull/140).

Expand All @@ -24,7 +24,7 @@ This API may be changed over time if the CI workflow making use of it is adjuste
- `2`: If an unexpected I/O error occurs
- Standard error:
- Informative messages
- Error messages if validation is not successful
- Detected problems if validation is not successful

## Validity checks

Expand Down
110 changes: 56 additions & 54 deletions pkgs/test/nixpkgs-check-by-name/src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::nixpkgs_problem::NixpkgsProblem;
use crate::structure;
use crate::utils::ErrorWriter;
use crate::validation::{self, Validation::Success};
use crate::Version;
use std::path::Path;

use anyhow::Context;
use serde::Deserialize;
use std::collections::HashMap;
use std::io;
use std::path::PathBuf;
use std::process;
use tempfile::NamedTempFile;
Expand Down Expand Up @@ -40,12 +40,12 @@ const EXPR: &str = include_str!("eval.nix");
/// Check that the Nixpkgs attribute values corresponding to the packages in pkgs/by-name are
/// of the form `callPackage <package_file> { ... }`.
/// See the `eval.nix` file for how this is achieved on the Nix side
pub fn check_values<W: io::Write>(
pub fn check_values(
version: Version,
error_writer: &mut ErrorWriter<W>,
nixpkgs: &structure::Nixpkgs,
nixpkgs_path: &Path,
package_names: Vec<String>,
eval_accessible_paths: Vec<&Path>,
) -> anyhow::Result<()> {
) -> validation::Result<()> {
// Write the list of packages we need to check into a temporary JSON file.
// This can then get read by the Nix evaluation.
let attrs_file = NamedTempFile::new().context("Failed to create a temporary file")?;
Expand All @@ -55,7 +55,7 @@ pub fn check_values<W: io::Write>(
// entry is needed.
let attrs_file_path = attrs_file.path().canonicalize()?;

serde_json::to_writer(&attrs_file, &nixpkgs.package_names).context(format!(
serde_json::to_writer(&attrs_file, &package_names).context(format!(
"Failed to serialise the package names to the temporary path {}",
attrs_file_path.display()
))?;
Expand Down Expand Up @@ -87,9 +87,9 @@ pub fn check_values<W: io::Write>(
.arg(&attrs_file_path)
// Same for the nixpkgs to test
.args(["--arg", "nixpkgsPath"])
.arg(&nixpkgs.path)
.arg(nixpkgs_path)
.arg("-I")
.arg(&nixpkgs.path);
.arg(nixpkgs_path);

// Also add extra paths that need to be accessible
for path in eval_accessible_paths {
Expand All @@ -111,52 +111,54 @@ pub fn check_values<W: io::Write>(
String::from_utf8_lossy(&result.stdout)
))?;

for package_name in &nixpkgs.package_names {
let relative_package_file = structure::Nixpkgs::relative_file_for_package(package_name);
let absolute_package_file = nixpkgs.path.join(&relative_package_file);
Ok(validation::sequence_(package_names.iter().map(
|package_name| {
let relative_package_file = structure::relative_file_for_package(package_name);
let absolute_package_file = nixpkgs_path.join(&relative_package_file);

if let Some(attribute_info) = actual_files.get(package_name) {
let valid = match &attribute_info.variant {
AttributeVariant::AutoCalled => true,
AttributeVariant::CallPackage { path, empty_arg } => {
let correct_file = if let Some(call_package_path) = path {
absolute_package_file == *call_package_path
} else {
false
};
// Only check for the argument to be non-empty if the version is V1 or
// higher
let non_empty = if version >= Version::V1 {
!empty_arg
} else {
true
};
correct_file && non_empty
}
AttributeVariant::Other => false,
};

if !valid {
error_writer.write(&format!(
"pkgs.{package_name}: This attribute is manually defined (most likely in pkgs/top-level/all-packages.nix), which is only allowed if the definition is of the form `pkgs.callPackage {} {{ ... }}` with a non-empty second argument.",
relative_package_file.display()
))?;
continue;
}
if let Some(attribute_info) = actual_files.get(package_name) {
let valid = match &attribute_info.variant {
AttributeVariant::AutoCalled => true,
AttributeVariant::CallPackage { path, empty_arg } => {
let correct_file = if let Some(call_package_path) = path {
absolute_package_file == *call_package_path
} else {
false
};
// Only check for the argument to be non-empty if the version is V1 or
// higher
let non_empty = if version >= Version::V1 {
!empty_arg
} else {
true
};
correct_file && non_empty
}
AttributeVariant::Other => false,
};

if !attribute_info.is_derivation {
error_writer.write(&format!(
"pkgs.{package_name}: This attribute defined by {} is not a derivation",
relative_package_file.display()
))?;
if !valid {
NixpkgsProblem::WrongCallPackage {
relative_package_file: relative_package_file.clone(),
package_name: package_name.clone(),
}
.into()
} else if !attribute_info.is_derivation {
NixpkgsProblem::NonDerivation {
relative_package_file: relative_package_file.clone(),
package_name: package_name.clone(),
}
.into()
} else {
Success(())
}
} else {
NixpkgsProblem::UndefinedAttr {
relative_package_file: relative_package_file.clone(),
package_name: package_name.clone(),
}
.into()
}
} else {
error_writer.write(&format!(
"pkgs.{package_name}: This attribute is not defined but it should be defined automatically as {}",
relative_package_file.display()
))?;
continue;
}
}
Ok(())
},
)))
}
46 changes: 28 additions & 18 deletions pkgs/test/nixpkgs-check-by-name/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
mod eval;
mod nixpkgs_problem;
mod references;
mod structure;
mod utils;
mod validation;

use crate::structure::check_structure;
use crate::validation::Validation::Failure;
use crate::validation::Validation::Success;
use anyhow::Context;
use clap::{Parser, ValueEnum};
use colored::Colorize;
use std::io;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use structure::Nixpkgs;
use utils::ErrorWriter;

/// Program to check the validity of pkgs/by-name
#[derive(Parser, Debug)]
Expand Down Expand Up @@ -63,8 +66,8 @@ fn main() -> ExitCode {
///
/// # Return value
/// - `Err(e)` if an I/O-related error `e` occurred.
/// - `Ok(false)` if the structure is invalid, all the structural errors have been written to `error_writer`.
/// - `Ok(true)` if the structure is valid, nothing will have been written to `error_writer`.
/// - `Ok(false)` if there are problems, all of which will be written to `error_writer`.
/// - `Ok(true)` if there are no problems
pub fn check_nixpkgs<W: io::Write>(
nixpkgs_path: &Path,
version: Version,
Expand All @@ -76,31 +79,38 @@ pub fn check_nixpkgs<W: io::Write>(
nixpkgs_path.display()
))?;

// Wraps the error_writer to print everything in red, and tracks whether anything was printed
// at all. Later used to figure out if the structure was valid or not.
let mut error_writer = ErrorWriter::new(error_writer);

if !nixpkgs_path.join(structure::BASE_SUBPATH).exists() {
let check_result = if !nixpkgs_path.join(utils::BASE_SUBPATH).exists() {
eprintln!(
"Given Nixpkgs path does not contain a {} subdirectory, no check necessary.",
structure::BASE_SUBPATH
utils::BASE_SUBPATH
);
Success(())
} else {
let nixpkgs = Nixpkgs::new(&nixpkgs_path, &mut error_writer)?;
match check_structure(&nixpkgs_path)? {
Failure(errors) => Failure(errors),
Success(package_names) =>
// Only if we could successfully parse the structure, we do the evaluation checks
{
eval::check_values(version, &nixpkgs_path, package_names, eval_accessible_paths)?
}
}
};

if error_writer.empty {
// Only if we could successfully parse the structure, we do the semantic checks
eval::check_values(version, &mut error_writer, &nixpkgs, eval_accessible_paths)?;
references::check_references(&mut error_writer, &nixpkgs)?;
match check_result {
Failure(errors) => {
for error in errors {
writeln!(error_writer, "{}", error.to_string().red())?
}
Ok(false)
}
Success(_) => Ok(true),
}
Ok(error_writer.empty)
}

#[cfg(test)]
mod tests {
use crate::check_nixpkgs;
use crate::structure;
use crate::utils;
use crate::Version;
use anyhow::Context;
use std::fs;
Expand Down Expand Up @@ -145,7 +155,7 @@ mod tests {
return Ok(());
}

let base = path.join(structure::BASE_SUBPATH);
let base = path.join(utils::BASE_SUBPATH);

fs::create_dir_all(base.join("fo/foo"))?;
fs::write(base.join("fo/foo/package.nix"), "{ someDrv }: someDrv")?;
Expand Down
Loading