Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Number every problem reported by nixpkgs-vet #109

Merged
merged 40 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a02cae8
main: use pretty_assertions to make test case failures easier to diag…
philiptaron Sep 9, 2024
8902031
Rename NixpkgsProblem to Problem
philiptaron Sep 4, 2024
cb5f8bf
NPV-100: attribute is not defined but it should be defined automatically
philiptaron Sep 4, 2024
9070097
NPV-101: attribute is not a derivation
philiptaron Sep 4, 2024
a562e93
NPV-102: attribute uses `_internalCallByNamePackageFile`
philiptaron Sep 4, 2024
379f051
NPV-103: attribute name position cannot be determined
philiptaron Sep 4, 2024
c4c723c
Remove ByNameError and ByNameErrorKind
philiptaron Sep 4, 2024
a9c236a
NPV-104: non-syntactic override of by-name package
philiptaron Sep 4, 2024
d37d322
Move read_dir_sorted into structure
philiptaron Sep 4, 2024
d4092be
Move BASE_SUBPATH and PACKAGE_NIX_FILENAME to structure
philiptaron Sep 4, 2024
7cf39eb
Rename utils.rs to location.rs now that it only contains LineIndex
philiptaron Sep 4, 2024
0c5e390
Introduce a Location struct for errors
philiptaron Sep 4, 2024
d0f229e
NPV-105: by-name override of ill-defined callPackage
philiptaron Sep 4, 2024
1c99b36
NPV-106: by-name override contains wrong callPackage path
philiptaron Sep 4, 2024
8a2d763
NPV-107: by-name override contains empty argument
philiptaron Sep 4, 2024
661f2d4
NPV-108: by-name override contains empty path
philiptaron Sep 4, 2024
def45b2
Remove ByNameOverrideError and ByNameOverrideErrorKind
philiptaron Sep 4, 2024
96b2d67
eval: use early returns to dedent by_name_override
philiptaron Sep 4, 2024
22c9237
problem: writing new manually is a drag
philiptaron Sep 8, 2024
5345db8
NPV-109: by-name shard is not a directory
philiptaron Sep 8, 2024
09c8000
NPV-110: by-name shard is invalid
philiptaron Sep 8, 2024
6dd1305
NPV-111: by-name shard is case-sensitive duplicate
philiptaron Sep 9, 2024
de1d3ce
NPV-120: Nix evaluation failed
philiptaron Sep 9, 2024
a5ab0c6
NPV-121: Nix file contains interpolated path
philiptaron Sep 9, 2024
c51d3ed
NPV-122: Nix file contains search path
philiptaron Sep 9, 2024
f58eeb8
NPV-123: Nix file contains path expression outside of directory
philiptaron Sep 9, 2024
7bd10c8
NPV-124: Nix file contains unresolvable path expression
philiptaron Sep 9, 2024
65bc10b
NPV-125: Package contains symlink pointing outside its directory
philiptaron Sep 9, 2024
f5dc1b3
NPV-126: Package contains unresolvable symlink
philiptaron Sep 9, 2024
dee54df
NPV-140: Package directory is not directory
philiptaron Sep 9, 2024
1ca4593
NPV-141: Package name is not valid
philiptaron Sep 9, 2024
d0050ad
NPV-142: Package is in the wrong by-name shard
philiptaron Sep 9, 2024
23e7968
NPV-143: `package.nix` is missing
philiptaron Sep 9, 2024
f79e09b
NPV-144: `package.nix` is not a file
philiptaron Sep 9, 2024
9cbfbc3
NPV-160: top-level package moved out of by-name
philiptaron Sep 9, 2024
b722a97
NPV-161: top-level package moved out of by-name with custom arguments
philiptaron Sep 9, 2024
d734d05
NPV-162: new top-level package should be in by-name
philiptaron Sep 9, 2024
39475f0
NPV-163: new top-level package should be in by-name with a custom arg…
philiptaron Sep 9, 2024
fe462fb
problem: use derive_more to implement Display
philiptaron Sep 9, 2024
8f7e0be
problem: for brevity and call-site clarity, prefix the various errors…
philiptaron Sep 9, 2024
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
52 changes: 52 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ rowan = "0.15.16"
indoc = "2.0.5"
relative-path = "1.9.3"
textwrap = "0.16.1"
derive-enum-from-into = "0.2.0"
derive-new = "0.7.0"
derive_more = { version = "1.0.0", features = ["display"] }

[dev-dependencies]
pretty_assertions = "1.4.0"
Expand Down
175 changes: 78 additions & 97 deletions src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
use crate::nix_file::CallPackageArgumentInfo;
use crate::nixpkgs_problem::{
ByNameError, ByNameErrorKind, ByNameOverrideError, ByNameOverrideErrorKind, NixEvalError,
NixpkgsProblem,
};
use crate::ratchet::RatchetState::{Loose, Tight};
use crate::ratchet::{self, ManualDefinition, RatchetState};
use crate::structure;
use crate::utils;
use crate::validation::ResultIteratorExt as _;
use crate::validation::{self, Validation::Success};
use crate::NixFileStore;
use relative_path::RelativePathBuf;
use std::path::{Path, PathBuf};
use std::{env, fs, process};

use anyhow::Context;
use relative_path::RelativePathBuf;
use serde::Deserialize;
use tempfile::Builder;

use crate::nix_file::CallPackageArgumentInfo;
use crate::problem::{
npv_100, npv_101, npv_102, npv_103, npv_104, npv_105, npv_106, npv_107, npv_108, npv_120,
};
use crate::ratchet::RatchetState::{Loose, Tight};
use crate::structure::{self, BASE_SUBPATH};
use crate::validation::ResultIteratorExt as _;
use crate::validation::{self, Validation::Success};
use crate::NixFileStore;
use crate::{location, ratchet};

const EVAL_NIX: &[u8] = include_bytes!("eval.nix");

/// Attribute set of this structure is returned by `./eval.nix`
Expand Down Expand Up @@ -59,16 +58,18 @@ struct Location {
}

impl Location {
/// Returns the [file] field, but relative to Nixpkgs.
fn relative_file(&self, nixpkgs_path: &Path) -> anyhow::Result<RelativePathBuf> {
let path = self.file.strip_prefix(nixpkgs_path).with_context(|| {
/// Returns the location, but relative to the given Nixpkgs root.
fn relative(self, nixpkgs_path: &Path) -> anyhow::Result<location::Location> {
let Self { file, line, column } = self;
let path = file.strip_prefix(nixpkgs_path).with_context(|| {
format!(
"The file ({}) is outside Nixpkgs ({})",
self.file.display(),
file.display(),
nixpkgs_path.display()
)
})?;
Ok(RelativePathBuf::from_path(path).expect("relative path"))
let relative_file = RelativePathBuf::from_path(path).expect("relative path");
Ok(location::Location::new(relative_file, line, column))
}
}

Expand Down Expand Up @@ -219,8 +220,7 @@ pub fn check_values(

if !result.status.success() {
// Early return in case evaluation fails
let stderr = String::from_utf8_lossy(&result.stderr).to_string();
return Ok(NixpkgsProblem::NixEval(NixEvalError { stderr }).into());
return Ok(npv_120::NixEvalError::new(String::from_utf8_lossy(&result.stderr)).into());
}

// Parse the resulting JSON value
Expand Down Expand Up @@ -268,29 +268,18 @@ fn by_name(
attribute_name: &str,
by_name_attribute: ByNameAttribute,
) -> validation::Result<ratchet::Package> {
use ratchet::RatchetState::*;
use ByNameAttribute::*;

let to_validation = |kind| -> validation::Validation<RatchetState<ManualDefinition>> {
NixpkgsProblem::ByName(ByNameError {
attribute_name: attribute_name.to_owned(),
kind,
})
.into()
};

// At this point we know that `pkgs/by-name/fo/foo/package.nix` has to exists. This match
// decides whether the attribute `foo` is defined accordingly and whether a legacy manual
// definition could be removed.
let manual_definition_result = match by_name_attribute {
// The attribute is missing.
Missing => {
ByNameAttribute::Missing => {
// This indicates a bug in the `pkgs/by-name` overlay, because it's supposed to
// automatically defined attributes in `pkgs/by-name`
to_validation(ByNameErrorKind::UndefinedAttr)
npv_100::ByNameUndefinedAttribute::new(attribute_name).into()
}
// The attribute exists
Existing(AttributeInfo {
ByNameAttribute::Existing(AttributeInfo {
// But it's not an attribute set, which limits the amount of information we can get
// about this attribute (see ./eval.nix)
attribute_variant: AttributeVariant::NonAttributeSet,
Expand All @@ -301,10 +290,10 @@ fn by_name(
//
// We can't know whether the attribute is automatically or manually defined for sure,
// and while we could check the location, the error seems clear enough as is.
to_validation(ByNameErrorKind::NonDerivation)
npv_101::ByNameNonDerivation::new(attribute_name).into()
}
// The attribute exists
Existing(AttributeInfo {
ByNameAttribute::Existing(AttributeInfo {
// And it's an attribute set, which allows us to get more information about it
attribute_variant:
AttributeVariant::AttributeSet {
Expand All @@ -317,7 +306,7 @@ fn by_name(
let is_derivation_result = if is_derivation {
Success(())
} else {
to_validation(ByNameErrorKind::NonDerivation).map(|_| ())
npv_101::ByNameNonDerivation::new(attribute_name).into()
};

// If the definition looks correct
Expand All @@ -330,7 +319,7 @@ fn by_name(
// Such an automatic definition should definitely not have a location.
// Having one indicates that somebody is using
// `_internalCallByNamePackageFile`,
to_validation(ByNameErrorKind::InternalCallPackageUsed)
npv_102::ByNameInternalCallPackageUsed::new(attribute_name).into()
} else {
Success(Tight)
}
Expand All @@ -348,13 +337,11 @@ fn by_name(
let nix_file = nix_file_store.get(&location.file)?;

// The relative path of the Nix file, for error messages
let relative_location_file =
location.relative_file(nixpkgs_path).with_context(|| {
format!(
"Failed to resolve the file where attribute {} is defined",
attribute_name
)
})?;
let location = location.relative(nixpkgs_path).with_context(|| {
format!(
"Failed to resolve the file where attribute {attribute_name} is defined"
)
})?;

// Figure out whether it's an attribute definition of the form
// `= callPackage <arg1> <arg2>`, returning the arguments if so.
Expand All @@ -377,13 +364,12 @@ fn by_name(
optional_syntactic_call_package,
definition,
location,
relative_location_file,
)
} else {
// If manual definitions don't have a location, it's likely `mapAttrs`'d
// over, e.g. if it's defined in aliases.nix.
// We can't verify whether its of the expected `callPackage`, so error out.
to_validation(ByNameErrorKind::CannotDetermineAttributeLocation)
npv_103::ByNameCannotDetermineAttributeLocation::new(attribute_name).into()
}
}
};
Expand Down Expand Up @@ -411,58 +397,53 @@ fn by_name_override(
is_semantic_call_package: bool,
optional_syntactic_call_package: Option<CallPackageArgumentInfo>,
definition: String,
location: Location,
relative_location_file: RelativePathBuf,
location: location::Location,
) -> validation::Validation<ratchet::RatchetState<ratchet::ManualDefinition>> {
let expected_package_path = structure::relative_file_for_package(attribute_name);

let to_problem = |kind| {
NixpkgsProblem::ByNameOverride(ByNameOverrideError {
package_name: attribute_name.to_owned(),
expected_package_path: expected_package_path.to_owned(),
file: relative_location_file,
line: location.line,
column: location.column,
let Some(syntactic_call_package) = optional_syntactic_call_package else {
// Something like `<attr> = foo`
return npv_104::ByNameOverrideOfNonSyntacticCallPackage::new(
attribute_name,
location,
definition,
kind,
})
)
.into();
};

// At this point, we completed two different checks for whether it's a `callPackage`
match (is_semantic_call_package, optional_syntactic_call_package) {
// Something like `<attr> = foo`
(_, None) => to_problem(ByNameOverrideErrorKind::NonSyntacticCallPackage).into(),

if !is_semantic_call_package {
// Something like `<attr> = pythonPackages.callPackage ...`
(false, Some(_)) => to_problem(ByNameOverrideErrorKind::NonToplevelCallPackage).into(),

// Something like `<attr> = pkgs.callPackage ...`
(true, Some(syntactic_call_package)) => {
if let Some(actual_package_path) = syntactic_call_package.relative_path {
if actual_package_path != expected_package_path {
// Wrong path
to_problem(ByNameOverrideErrorKind::WrongCallPackagePath {
actual_path: actual_package_path,
})
.into()
} else {
// Manual definitions with empty arguments are not allowed anymore,
// but existing ones should continue to be allowed.
let manual_definition_ratchet = if syntactic_call_package.empty_arg {
// This is the state to migrate away from.
Loose(to_problem(ByNameOverrideErrorKind::EmptyArgument))
} else {
// This is the state to migrate to.
Tight
};
return npv_105::ByNameOverrideOfNonTopLevelPackage::new(
attribute_name,
location,
definition,
)
.into();
}

Success(manual_definition_ratchet)
}
} else {
// No path...
to_problem(ByNameOverrideErrorKind::NonPath).into()
}
}
let Some(actual_package_path) = syntactic_call_package.relative_path else {
return npv_108::ByNameOverrideContainsEmptyPath::new(attribute_name, location, definition)
.into();
};

let expected_package_path = structure::relative_file_for_package(attribute_name);
if actual_package_path != expected_package_path {
return npv_106::ByNameOverrideContainsWrongCallPackagePath::new(
attribute_name,
actual_package_path,
location,
)
.into();
}

// Manual definitions with empty arguments are not allowed anymore, but existing ones should
// continue to be allowed. This is the state to migrate away from.
if syntactic_call_package.empty_arg {
Success(Loose(
npv_107::ByNameOverrideContainsEmptyArgument::new(attribute_name, location, definition)
.into(),
))
} else {
// This is the state to migrate to.
Success(Tight)
}
}

Expand Down Expand Up @@ -530,8 +511,8 @@ fn handle_non_by_name_attribute(
// Parse the Nix file in the location
let nix_file = nix_file_store.get(&location.file)?;

// The relative path of the Nix file, for error messages
let relative_location_file = location.relative_file(nixpkgs_path).with_context(|| {
// The relative location of the Nix file, for error messages
let location = location.relative(nixpkgs_path).with_context(|| {
format!("Failed to resolve the file where attribute {attribute_name} is defined")
})?;

Expand Down Expand Up @@ -567,7 +548,7 @@ fn handle_non_by_name_attribute(
(true, Some(syntactic_call_package)) => {
// It's only possible to migrate such a definitions if..
match syntactic_call_package.relative_path {
Some(ref rel_path) if rel_path.starts_with(utils::BASE_SUBPATH) => {
Some(ref rel_path) if rel_path.starts_with(BASE_SUBPATH) => {
// ..the path is not already within `pkgs/by-name` like
//
// foo-variant = callPackage ../by-name/fo/foo/package.nix {
Expand All @@ -585,7 +566,7 @@ fn handle_non_by_name_attribute(
_ => {
// Otherwise, the path is outside `pkgs/by-name`, which means it can be
// migrated.
Loose((syntactic_call_package, relative_location_file))
Loose((syntactic_call_package, location.file))
}
}
}
Expand Down
Loading