Skip to content

Comments

nixpkgs-check-by-name: Intermediate error representation refactor#261939

Merged
infinisil merged 28 commits intoNixOS:masterfrom
tweag:check-by-name-intermediate
Oct 30, 2023
Merged

nixpkgs-check-by-name: Intermediate error representation refactor#261939
infinisil merged 28 commits intoNixOS:masterfrom
tweag:check-by-name-intermediate

Conversation

@infinisil
Copy link
Member

@infinisil infinisil commented Oct 19, 2023

Description of changes

This is motivated by #256788 (comment), which would be hard to implement in the current code base. Also this just makes things nicer.

This will be essentially a rewrite of the tool, but without any functional changes, I won't touch the test suite.

The commits are kept fairly small and are decently reviewable. Not crucial for anybody to take a super close look though, the tests pass for each commit.

Thought: This should really live in a separate repository..

Ping the other members of the NAT for review: @roberth, @Ericson2314, @tomberek, @DavHau, @aakropotkin, @phaer

This work is sponsored by Antithesis

Things done

  • Ran tests successfully
  • Documented code
  • Built nix-build -A tests.nixpkgs-check-by-name successfully on x86_64-linux

@infinisil infinisil added this to the RFC 140 milestone Oct 19, 2023
@ofborg ofborg bot added 10.rebuild-darwin: 1-10 This PR causes between 1 and 10 packages to rebuild on Darwin. 10.rebuild-darwin: 1 This PR causes 1 package to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 10.rebuild-linux: 1 This PR causes 1 package to rebuild on Linux. labels Oct 19, 2023
Currently the tool prints problems right as it is checking the code
without an intermediate error representation. However for various reasons
it would be beneficial to have an intermediate error type:
- It makes the code cleaner, having all errors in one place
- It allows printing the error in different ways, e.g. for a future
  --json mode

This commit prepares for an incremental refactoring for an intermediate
error/problem representation. Most notable is that we want to be able to collect
multiple errors/problems and not just exit on the first one.

We introduce the type alias CheckResult and CheckError (later renamed to
NixpkgsProblem), where CheckError allows collecting multiple
CheckErrors using the utility function flatten_check_results (later
renamed to check_result::sequence)

The write_check_result function is only temporarily introduced to help
refactoring, it's removed again in later commits.
@infinisil infinisil force-pushed the check-by-name-intermediate branch from 655f147 to 568840b Compare October 23, 2023 23:16
@infinisil infinisil force-pushed the check-by-name-intermediate branch from 568840b to 03c58ad Compare October 23, 2023 23:19
@infinisil infinisil changed the title Refactor nixpkgs-check-by-name to have an intermediate error representation nixpkgs-check-by-name: Intermediate error representation refactor Oct 23, 2023
@infinisil infinisil marked this pull request as ready for review October 23, 2023 23:24
Copy link
Contributor

@whentze whentze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall, but I left some comments.

Comment on lines 9 to 18
/// A type alias representing the result of a check, either:
/// - Err(anyhow::Error): A fatal failure, typically I/O errors.
/// Such failures are not caused by the files in Nixpkgs.
/// This hints at a bug in the code or a problem with the deployment.
/// - Ok(Left(Vec<NixpkgsProblem>)): A non-fatal problem with the Nixpkgs files.
/// Further checks can be run even with this result type.
/// Such problems can be fixed by changing the Nixpkgs files.
/// - Ok(Right(A)): A successful (potentially intermediate) result with an arbitrary value.
/// No fatal errors have occurred and no problems have been found with Nixpkgs.
pub type CheckResult<A> = anyhow::Result<Either<Vec<NixpkgsProblem>, A>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but is a bit clumsy I feel. You have the Either that is documented nicely here but the meaning of left/right is lost at all the usage sites, and you have something that is ostensibly an error case in your Ok variant, which in turn begets the custom utility functions like map and ok below.

Potential alternative: Make a new Error type like

enum CheckError {
  Fatal(anyhow::Error),
  NonFatal(Vec<NixpkgsProblem>),
}

then implement From<anyhow::Error> for it so you get to keep using ? ergonomically.
Your CheckResult would then just be Result<CheckError>, and you can use the regular map and Ok from vanilla Result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For others: I paired with @whentze today, where we concluded that this doesn't work out very well, in short, the Result type is made for early exits, but in our case Err(NonFatal(_)) should not exit early, which makes the code rather complicated.

Instead we ended up going for:

  • A new type Validation (leaning onto Haskell's Validation, which inspired this in the first place) instead of Either
  • Handling Result's as they happen in the checks using ?, so this complexity is not in the Validation functions

There are now somewhat ugly Ok(...)'s wrapping parts of the code, but it's not terrible imo :)

Comment on lines 31 to 36
impl NixpkgsProblem {
/// Create a `CheckResult<A>` from a single check problem
pub fn into_result<A>(self) -> CheckResult<A> {
Ok(Left(vec![self]))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, if you use vanilla Result you can impl From<NixpkgsProblem> for CheckError and get to use ?.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

struct PackageContext<'a> {
/// The package directory relative to Nixpkgs, such as `pkgs/by-name/fo/foo`
relative_package_dir: &'a PathBuf,
/// The absolute package directory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't touch this in this PR, but it causes the new code below to be somewhat needlessly verbose:

It is almost always a bad idea to have & to the owned versions of sequences, e.g. &Vec<T>, &String, &PathBuf.
You don't get any extra powers over just using the borrowed version, i.e. &[T], &str, &Path, because you still can't mutate/move the thing. You still need to drag that lifetime parameter around, and you have a double indirection (in terms of pointers) when a single one would've sufficed.
And as you see in the code below, you now need some extra verbosity to turn &Path into &PathBuf at various points.

I'd suggest restricting yourself to using only PathBuf and &'a Path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I now removed this struct entirely, it wasn't necessary anymore anyways, but this is good to remember for the future.

second: r.file_name(),
}
.into_result::<()>()
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since itertools are already pulled in, could you use duplicates_by here to make it a bit cleaner?

Maybe the Hash bound is a problem though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the duplicates_by iterator only contains one duplicate and not two, which we need for the error here, so this doesn't quite work unfortunately.

@infinisil infinisil requested a review from whentze October 24, 2023 18:08
@delroth delroth added the 12.approvals: 1 This PR was reviewed and approved by one person. label Oct 24, 2023
@infinisil infinisil merged commit 4651ac9 into NixOS:master Oct 30, 2023
@infinisil infinisil deleted the check-by-name-intermediate branch October 30, 2023 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

10.rebuild-darwin: 1-10 This PR causes between 1 and 10 packages to rebuild on Darwin. 10.rebuild-darwin: 1 This PR causes 1 package to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 10.rebuild-linux: 1 This PR causes 1 package to rebuild on Linux. 12.approvals: 1 This PR was reviewed and approved by one person.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants