···2222use IPC::Cmd;
2323use Sys::Syslog qw(:standard :macros);
2424use Cwd qw(abs_path);
2525+use Fcntl ':flock';
25262627## no critic(ControlStructures::ProhibitDeepNests)
2728## no critic(ErrorHandling::RequireCarping)
···9192}
92939394make_path("/run/nixos", { mode => oct(755) });
9595+open(my $stc_lock, '>>', '/run/nixos/switch-to-configuration.lock') or die "Could not open lock - $!";
9696+flock($stc_lock, LOCK_EX) or die "Could not acquire lock - $!";
9497openlog("nixos", "", LOG_USER);
95989699# Install or update the bootloader.
···985988 syslog(LOG_ERR, "switching to system configuration $toplevel failed (status $res)");
986989}
987990991991+close($stc_lock) or die "Could not close lock - $!";
988992exit($res);
···66 # NOTE: When updating these, please also take a look at the changes done to
77 # kernel config in the xanmod version commit
88 ltsVariant = {
99- version = "6.1.58";
1010- hash = "sha256-Lnp1CSh1jLbIkEx9hLfxhdIA12iQZmywhOec9uZ7UjI=";
99+ version = "6.1.60";
1010+ hash = "sha256-KYCeONJxyFPee4pvBLRw/MBTzPU7D2oZCrAVr3t/yPM=";
1111 variant = "lts";
1212 };
13131414 mainVariant = {
1515- version = "6.5.8";
1616- hash = "sha256-lHi+O7RE6YdiqPmuxHajGkc7jS9F5cB89+JbTVKkB/c=";
1515+ version = "6.5.9";
1616+ hash = "sha256-5SFPBsDTmq7tA6pyM7rbIjBPAtPbqhUl6VfA2z5baPA=";
1717 variant = "main";
1818 };
1919
+2-4
pkgs/servers/calibre-web/default.nix
···2525in
2626python.pkgs.buildPythonApplication rec {
2727 pname = "calibre-web";
2828- version = "0.6.20";
2828+ version = "0.6.21";
29293030 src = fetchFromGitHub {
3131 owner = "janeczku";
3232 repo = "calibre-web";
3333 rev = version;
3434- hash = "sha256-0lArY1aTpO4sgIVDSqClYMGlip92f9hE/L2UouTLK8Q=";
3434+ hash = "sha256-tRrOquetn3P2NmrXq7DQHRGP1sWnLR7bV2Lw0W/lUPQ=";
3535 };
36363737 propagatedBuildInputs = with python.pkgs; [
···6464 # and exit. This is gonna be used to configure calibre-web declaratively, as most of its configuration parameters
6565 # are stored in the DB.
6666 ./db-migrations.patch
6767- # environ in tornado.wsgi.WSGIContainer no longer a static method from 6.3 version
6868- ./static_environ.patch
6967 ];
70687169 # calibre-web doesn't follow setuptools directory structure. The following is taken from the script
···11# Nixpkgs pkgs/by-name checker
2233-This directory implements a program to check the [validity](#validity-checks) of the `pkgs/by-name` Nixpkgs directory once introduced.
33+This directory implements a program to check the [validity](#validity-checks) of the `pkgs/by-name` Nixpkgs directory.
44It is being used by [this GitHub Actions workflow](../../../.github/workflows/check-by-name.yml).
55This is part of the implementation of [RFC 140](https://github.com/NixOS/rfcs/pull/140).
66···2424 - `2`: If an unexpected I/O error occurs
2525- Standard error:
2626 - Informative messages
2727- - Error messages if validation is not successful
2727+ - Detected problems if validation is not successful
28282929## Validity checks
3030
+56-54
pkgs/test/nixpkgs-check-by-name/src/eval.rs
···11+use crate::nixpkgs_problem::NixpkgsProblem;
12use crate::structure;
22-use crate::utils::ErrorWriter;
33+use crate::validation::{self, Validation::Success};
34use crate::Version;
45use std::path::Path;
5667use anyhow::Context;
78use serde::Deserialize;
89use std::collections::HashMap;
99-use std::io;
1010use std::path::PathBuf;
1111use std::process;
1212use tempfile::NamedTempFile;
···4040/// Check that the Nixpkgs attribute values corresponding to the packages in pkgs/by-name are
4141/// of the form `callPackage <package_file> { ... }`.
4242/// See the `eval.nix` file for how this is achieved on the Nix side
4343-pub fn check_values<W: io::Write>(
4343+pub fn check_values(
4444 version: Version,
4545- error_writer: &mut ErrorWriter<W>,
4646- nixpkgs: &structure::Nixpkgs,
4545+ nixpkgs_path: &Path,
4646+ package_names: Vec<String>,
4747 eval_accessible_paths: Vec<&Path>,
4848-) -> anyhow::Result<()> {
4848+) -> validation::Result<()> {
4949 // Write the list of packages we need to check into a temporary JSON file.
5050 // This can then get read by the Nix evaluation.
5151 let attrs_file = NamedTempFile::new().context("Failed to create a temporary file")?;
···5555 // entry is needed.
5656 let attrs_file_path = attrs_file.path().canonicalize()?;
57575858- serde_json::to_writer(&attrs_file, &nixpkgs.package_names).context(format!(
5858+ serde_json::to_writer(&attrs_file, &package_names).context(format!(
5959 "Failed to serialise the package names to the temporary path {}",
6060 attrs_file_path.display()
6161 ))?;
···8787 .arg(&attrs_file_path)
8888 // Same for the nixpkgs to test
8989 .args(["--arg", "nixpkgsPath"])
9090- .arg(&nixpkgs.path)
9090+ .arg(nixpkgs_path)
9191 .arg("-I")
9292- .arg(&nixpkgs.path);
9292+ .arg(nixpkgs_path);
93939494 // Also add extra paths that need to be accessible
9595 for path in eval_accessible_paths {
···111111 String::from_utf8_lossy(&result.stdout)
112112 ))?;
113113114114- for package_name in &nixpkgs.package_names {
115115- let relative_package_file = structure::Nixpkgs::relative_file_for_package(package_name);
116116- let absolute_package_file = nixpkgs.path.join(&relative_package_file);
114114+ Ok(validation::sequence_(package_names.iter().map(
115115+ |package_name| {
116116+ let relative_package_file = structure::relative_file_for_package(package_name);
117117+ let absolute_package_file = nixpkgs_path.join(&relative_package_file);
117118118118- if let Some(attribute_info) = actual_files.get(package_name) {
119119- let valid = match &attribute_info.variant {
120120- AttributeVariant::AutoCalled => true,
121121- AttributeVariant::CallPackage { path, empty_arg } => {
122122- let correct_file = if let Some(call_package_path) = path {
123123- absolute_package_file == *call_package_path
124124- } else {
125125- false
126126- };
127127- // Only check for the argument to be non-empty if the version is V1 or
128128- // higher
129129- let non_empty = if version >= Version::V1 {
130130- !empty_arg
131131- } else {
132132- true
133133- };
134134- correct_file && non_empty
135135- }
136136- AttributeVariant::Other => false,
137137- };
138138-139139- if !valid {
140140- error_writer.write(&format!(
141141- "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.",
142142- relative_package_file.display()
143143- ))?;
144144- continue;
145145- }
119119+ if let Some(attribute_info) = actual_files.get(package_name) {
120120+ let valid = match &attribute_info.variant {
121121+ AttributeVariant::AutoCalled => true,
122122+ AttributeVariant::CallPackage { path, empty_arg } => {
123123+ let correct_file = if let Some(call_package_path) = path {
124124+ absolute_package_file == *call_package_path
125125+ } else {
126126+ false
127127+ };
128128+ // Only check for the argument to be non-empty if the version is V1 or
129129+ // higher
130130+ let non_empty = if version >= Version::V1 {
131131+ !empty_arg
132132+ } else {
133133+ true
134134+ };
135135+ correct_file && non_empty
136136+ }
137137+ AttributeVariant::Other => false,
138138+ };
146139147147- if !attribute_info.is_derivation {
148148- error_writer.write(&format!(
149149- "pkgs.{package_name}: This attribute defined by {} is not a derivation",
150150- relative_package_file.display()
151151- ))?;
140140+ if !valid {
141141+ NixpkgsProblem::WrongCallPackage {
142142+ relative_package_file: relative_package_file.clone(),
143143+ package_name: package_name.clone(),
144144+ }
145145+ .into()
146146+ } else if !attribute_info.is_derivation {
147147+ NixpkgsProblem::NonDerivation {
148148+ relative_package_file: relative_package_file.clone(),
149149+ package_name: package_name.clone(),
150150+ }
151151+ .into()
152152+ } else {
153153+ Success(())
154154+ }
155155+ } else {
156156+ NixpkgsProblem::UndefinedAttr {
157157+ relative_package_file: relative_package_file.clone(),
158158+ package_name: package_name.clone(),
159159+ }
160160+ .into()
152161 }
153153- } else {
154154- error_writer.write(&format!(
155155- "pkgs.{package_name}: This attribute is not defined but it should be defined automatically as {}",
156156- relative_package_file.display()
157157- ))?;
158158- continue;
159159- }
160160- }
161161- Ok(())
162162+ },
163163+ )))
162164}
+28-18
pkgs/test/nixpkgs-check-by-name/src/main.rs
···11mod eval;
22+mod nixpkgs_problem;
23mod references;
34mod structure;
45mod utils;
66+mod validation;
5788+use crate::structure::check_structure;
99+use crate::validation::Validation::Failure;
1010+use crate::validation::Validation::Success;
611use anyhow::Context;
712use clap::{Parser, ValueEnum};
813use colored::Colorize;
914use std::io;
1015use std::path::{Path, PathBuf};
1116use std::process::ExitCode;
1212-use structure::Nixpkgs;
1313-use utils::ErrorWriter;
14171518/// Program to check the validity of pkgs/by-name
1619#[derive(Parser, Debug)]
···6366///
6467/// # Return value
6568/// - `Err(e)` if an I/O-related error `e` occurred.
6666-/// - `Ok(false)` if the structure is invalid, all the structural errors have been written to `error_writer`.
6767-/// - `Ok(true)` if the structure is valid, nothing will have been written to `error_writer`.
6969+/// - `Ok(false)` if there are problems, all of which will be written to `error_writer`.
7070+/// - `Ok(true)` if there are no problems
6871pub fn check_nixpkgs<W: io::Write>(
6972 nixpkgs_path: &Path,
7073 version: Version,
···7679 nixpkgs_path.display()
7780 ))?;
78817979- // Wraps the error_writer to print everything in red, and tracks whether anything was printed
8080- // at all. Later used to figure out if the structure was valid or not.
8181- let mut error_writer = ErrorWriter::new(error_writer);
8282-8383- if !nixpkgs_path.join(structure::BASE_SUBPATH).exists() {
8282+ let check_result = if !nixpkgs_path.join(utils::BASE_SUBPATH).exists() {
8483 eprintln!(
8584 "Given Nixpkgs path does not contain a {} subdirectory, no check necessary.",
8686- structure::BASE_SUBPATH
8585+ utils::BASE_SUBPATH
8786 );
8787+ Success(())
8888 } else {
8989- let nixpkgs = Nixpkgs::new(&nixpkgs_path, &mut error_writer)?;
8989+ match check_structure(&nixpkgs_path)? {
9090+ Failure(errors) => Failure(errors),
9191+ Success(package_names) =>
9292+ // Only if we could successfully parse the structure, we do the evaluation checks
9393+ {
9494+ eval::check_values(version, &nixpkgs_path, package_names, eval_accessible_paths)?
9595+ }
9696+ }
9797+ };
90989191- if error_writer.empty {
9292- // Only if we could successfully parse the structure, we do the semantic checks
9393- eval::check_values(version, &mut error_writer, &nixpkgs, eval_accessible_paths)?;
9494- references::check_references(&mut error_writer, &nixpkgs)?;
9999+ match check_result {
100100+ Failure(errors) => {
101101+ for error in errors {
102102+ writeln!(error_writer, "{}", error.to_string().red())?
103103+ }
104104+ Ok(false)
95105 }
106106+ Success(_) => Ok(true),
96107 }
9797- Ok(error_writer.empty)
98108}
99109100110#[cfg(test)]
101111mod tests {
102112 use crate::check_nixpkgs;
103103- use crate::structure;
113113+ use crate::utils;
104114 use crate::Version;
105115 use anyhow::Context;
106116 use std::fs;
···145155 return Ok(());
146156 }
147157148148- let base = path.join(structure::BASE_SUBPATH);
158158+ let base = path.join(utils::BASE_SUBPATH);
149159150160 fs::create_dir_all(base.join("fo/foo"))?;
151161 fs::write(base.join("fo/foo/package.nix"), "{ someDrv }: someDrv")?;
···11+use crate::utils::PACKAGE_NIX_FILENAME;
22+use rnix::parser::ParseError;
33+use std::ffi::OsString;
44+use std::fmt;
55+use std::io;
66+use std::path::PathBuf;
77+88+/// Any problem that can occur when checking Nixpkgs
99+pub enum NixpkgsProblem {
1010+ ShardNonDir {
1111+ relative_shard_path: PathBuf,
1212+ },
1313+ InvalidShardName {
1414+ relative_shard_path: PathBuf,
1515+ shard_name: String,
1616+ },
1717+ PackageNonDir {
1818+ relative_package_dir: PathBuf,
1919+ },
2020+ CaseSensitiveDuplicate {
2121+ relative_shard_path: PathBuf,
2222+ first: OsString,
2323+ second: OsString,
2424+ },
2525+ InvalidPackageName {
2626+ relative_package_dir: PathBuf,
2727+ package_name: String,
2828+ },
2929+ IncorrectShard {
3030+ relative_package_dir: PathBuf,
3131+ correct_relative_package_dir: PathBuf,
3232+ },
3333+ PackageNixNonExistent {
3434+ relative_package_dir: PathBuf,
3535+ },
3636+ PackageNixDir {
3737+ relative_package_dir: PathBuf,
3838+ },
3939+ UndefinedAttr {
4040+ relative_package_file: PathBuf,
4141+ package_name: String,
4242+ },
4343+ WrongCallPackage {
4444+ relative_package_file: PathBuf,
4545+ package_name: String,
4646+ },
4747+ NonDerivation {
4848+ relative_package_file: PathBuf,
4949+ package_name: String,
5050+ },
5151+ OutsideSymlink {
5252+ relative_package_dir: PathBuf,
5353+ subpath: PathBuf,
5454+ },
5555+ UnresolvableSymlink {
5656+ relative_package_dir: PathBuf,
5757+ subpath: PathBuf,
5858+ io_error: io::Error,
5959+ },
6060+ CouldNotParseNix {
6161+ relative_package_dir: PathBuf,
6262+ subpath: PathBuf,
6363+ error: ParseError,
6464+ },
6565+ PathInterpolation {
6666+ relative_package_dir: PathBuf,
6767+ subpath: PathBuf,
6868+ line: usize,
6969+ text: String,
7070+ },
7171+ SearchPath {
7272+ relative_package_dir: PathBuf,
7373+ subpath: PathBuf,
7474+ line: usize,
7575+ text: String,
7676+ },
7777+ OutsidePathReference {
7878+ relative_package_dir: PathBuf,
7979+ subpath: PathBuf,
8080+ line: usize,
8181+ text: String,
8282+ },
8383+ UnresolvablePathReference {
8484+ relative_package_dir: PathBuf,
8585+ subpath: PathBuf,
8686+ line: usize,
8787+ text: String,
8888+ io_error: io::Error,
8989+ },
9090+}
9191+9292+impl fmt::Display for NixpkgsProblem {
9393+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9494+ match self {
9595+ NixpkgsProblem::ShardNonDir { relative_shard_path } =>
9696+ write!(
9797+ f,
9898+ "{}: This is a file, but it should be a directory.",
9999+ relative_shard_path.display(),
100100+ ),
101101+ NixpkgsProblem::InvalidShardName { relative_shard_path, shard_name } =>
102102+ write!(
103103+ f,
104104+ "{}: Invalid directory name \"{shard_name}\", must be at most 2 ASCII characters consisting of a-z, 0-9, \"-\" or \"_\".",
105105+ relative_shard_path.display()
106106+ ),
107107+ NixpkgsProblem::PackageNonDir { relative_package_dir } =>
108108+ write!(
109109+ f,
110110+ "{}: This path is a file, but it should be a directory.",
111111+ relative_package_dir.display(),
112112+ ),
113113+ NixpkgsProblem::CaseSensitiveDuplicate { relative_shard_path, first, second } =>
114114+ write!(
115115+ f,
116116+ "{}: Duplicate case-sensitive package directories {first:?} and {second:?}.",
117117+ relative_shard_path.display(),
118118+ ),
119119+ NixpkgsProblem::InvalidPackageName { relative_package_dir, package_name } =>
120120+ write!(
121121+ f,
122122+ "{}: Invalid package directory name \"{package_name}\", must be ASCII characters consisting of a-z, A-Z, 0-9, \"-\" or \"_\".",
123123+ relative_package_dir.display(),
124124+ ),
125125+ NixpkgsProblem::IncorrectShard { relative_package_dir, correct_relative_package_dir } =>
126126+ write!(
127127+ f,
128128+ "{}: Incorrect directory location, should be {} instead.",
129129+ relative_package_dir.display(),
130130+ correct_relative_package_dir.display(),
131131+ ),
132132+ NixpkgsProblem::PackageNixNonExistent { relative_package_dir } =>
133133+ write!(
134134+ f,
135135+ "{}: Missing required \"{PACKAGE_NIX_FILENAME}\" file.",
136136+ relative_package_dir.display(),
137137+ ),
138138+ NixpkgsProblem::PackageNixDir { relative_package_dir } =>
139139+ write!(
140140+ f,
141141+ "{}: \"{PACKAGE_NIX_FILENAME}\" must be a file.",
142142+ relative_package_dir.display(),
143143+ ),
144144+ NixpkgsProblem::UndefinedAttr { relative_package_file, package_name } =>
145145+ write!(
146146+ f,
147147+ "pkgs.{package_name}: This attribute is not defined but it should be defined automatically as {}",
148148+ relative_package_file.display()
149149+ ),
150150+ NixpkgsProblem::WrongCallPackage { relative_package_file, package_name } =>
151151+ write!(
152152+ f,
153153+ "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.",
154154+ relative_package_file.display()
155155+ ),
156156+ NixpkgsProblem::NonDerivation { relative_package_file, package_name } =>
157157+ write!(
158158+ f,
159159+ "pkgs.{package_name}: This attribute defined by {} is not a derivation",
160160+ relative_package_file.display()
161161+ ),
162162+ NixpkgsProblem::OutsideSymlink { relative_package_dir, subpath } =>
163163+ write!(
164164+ f,
165165+ "{}: Path {} is a symlink pointing to a path outside the directory of that package.",
166166+ relative_package_dir.display(),
167167+ subpath.display(),
168168+ ),
169169+ NixpkgsProblem::UnresolvableSymlink { relative_package_dir, subpath, io_error } =>
170170+ write!(
171171+ f,
172172+ "{}: Path {} is a symlink which cannot be resolved: {io_error}.",
173173+ relative_package_dir.display(),
174174+ subpath.display(),
175175+ ),
176176+ NixpkgsProblem::CouldNotParseNix { relative_package_dir, subpath, error } =>
177177+ write!(
178178+ f,
179179+ "{}: File {} could not be parsed by rnix: {}",
180180+ relative_package_dir.display(),
181181+ subpath.display(),
182182+ error,
183183+ ),
184184+ NixpkgsProblem::PathInterpolation { relative_package_dir, subpath, line, text } =>
185185+ write!(
186186+ f,
187187+ "{}: File {} at line {line} contains the path expression \"{}\", which is not yet supported and may point outside the directory of that package.",
188188+ relative_package_dir.display(),
189189+ subpath.display(),
190190+ text
191191+ ),
192192+ NixpkgsProblem::SearchPath { relative_package_dir, subpath, line, text } =>
193193+ write!(
194194+ f,
195195+ "{}: File {} at line {line} contains the nix search path expression \"{}\" which may point outside the directory of that package.",
196196+ relative_package_dir.display(),
197197+ subpath.display(),
198198+ text
199199+ ),
200200+ NixpkgsProblem::OutsidePathReference { relative_package_dir, subpath, line, text } =>
201201+ write!(
202202+ f,
203203+ "{}: File {} at line {line} contains the path expression \"{}\" which may point outside the directory of that package.",
204204+ relative_package_dir.display(),
205205+ subpath.display(),
206206+ text,
207207+ ),
208208+ NixpkgsProblem::UnresolvablePathReference { relative_package_dir, subpath, line, text, io_error } =>
209209+ write!(
210210+ f,
211211+ "{}: File {} at line {line} contains the path expression \"{}\" which cannot be resolved: {io_error}.",
212212+ relative_package_dir.display(),
213213+ subpath.display(),
214214+ text,
215215+ ),
216216+ }
217217+ }
218218+}
+118-127
pkgs/test/nixpkgs-check-by-name/src/references.rs
···11-use crate::structure::Nixpkgs;
11+use crate::nixpkgs_problem::NixpkgsProblem;
22use crate::utils;
33-use crate::utils::{ErrorWriter, LineIndex};
33+use crate::utils::LineIndex;
44+use crate::validation::{self, ResultIteratorExt, Validation::Success};
4556use anyhow::Context;
67use rnix::{Root, SyntaxKind::NODE_PATH};
78use std::ffi::OsStr;
89use std::fs::read_to_string;
99-use std::io;
1010-use std::path::{Path, PathBuf};
1111-1212-/// Small helper so we don't need to pass in the same arguments to all functions
1313-struct PackageContext<'a, W: io::Write> {
1414- error_writer: &'a mut ErrorWriter<W>,
1515- /// The package directory relative to Nixpkgs, such as `pkgs/by-name/fo/foo`
1616- relative_package_dir: &'a PathBuf,
1717- /// The absolute package directory
1818- absolute_package_dir: &'a PathBuf,
1919-}
1010+use std::path::Path;
20112112/// Check that every package directory in pkgs/by-name doesn't link to outside that directory.
2213/// Both symlinks and Nix path expressions are checked.
2323-pub fn check_references<W: io::Write>(
2424- error_writer: &mut ErrorWriter<W>,
2525- nixpkgs: &Nixpkgs,
2626-) -> anyhow::Result<()> {
2727- // Check the directories for each package separately
2828- for package_name in &nixpkgs.package_names {
2929- let relative_package_dir = Nixpkgs::relative_dir_for_package(package_name);
3030- let mut context = PackageContext {
3131- error_writer,
3232- relative_package_dir: &relative_package_dir,
3333- absolute_package_dir: &nixpkgs.path.join(&relative_package_dir),
3434- };
3535-3636- // The empty argument here is the subpath under the package directory to check
3737- // An empty one means the package directory itself
3838- check_path(&mut context, Path::new("")).context(format!(
3939- "While checking the references in package directory {}",
4040- relative_package_dir.display()
4141- ))?;
4242- }
4343- Ok(())
1414+pub fn check_references(
1515+ relative_package_dir: &Path,
1616+ absolute_package_dir: &Path,
1717+) -> validation::Result<()> {
1818+ // The empty argument here is the subpath under the package directory to check
1919+ // An empty one means the package directory itself
2020+ check_path(relative_package_dir, absolute_package_dir, Path::new("")).context(format!(
2121+ "While checking the references in package directory {}",
2222+ relative_package_dir.display()
2323+ ))
4424}
45254626/// Checks for a specific path to not have references outside
4747-fn check_path<W: io::Write>(context: &mut PackageContext<W>, subpath: &Path) -> anyhow::Result<()> {
4848- let path = context.absolute_package_dir.join(subpath);
2727+fn check_path(
2828+ relative_package_dir: &Path,
2929+ absolute_package_dir: &Path,
3030+ subpath: &Path,
3131+) -> validation::Result<()> {
3232+ let path = absolute_package_dir.join(subpath);
49335050- if path.is_symlink() {
3434+ Ok(if path.is_symlink() {
5135 // Check whether the symlink resolves to outside the package directory
5236 match path.canonicalize() {
5337 Ok(target) => {
5438 // No need to handle the case of it being inside the directory, since we scan through the
5539 // entire directory recursively anyways
5656- if let Err(_prefix_error) = target.strip_prefix(context.absolute_package_dir) {
5757- context.error_writer.write(&format!(
5858- "{}: Path {} is a symlink pointing to a path outside the directory of that package.",
5959- context.relative_package_dir.display(),
6060- subpath.display(),
6161- ))?;
4040+ if let Err(_prefix_error) = target.strip_prefix(absolute_package_dir) {
4141+ NixpkgsProblem::OutsideSymlink {
4242+ relative_package_dir: relative_package_dir.to_path_buf(),
4343+ subpath: subpath.to_path_buf(),
4444+ }
4545+ .into()
4646+ } else {
4747+ Success(())
6248 }
6349 }
6464- Err(e) => {
6565- context.error_writer.write(&format!(
6666- "{}: Path {} is a symlink which cannot be resolved: {e}.",
6767- context.relative_package_dir.display(),
6868- subpath.display(),
6969- ))?;
5050+ Err(io_error) => NixpkgsProblem::UnresolvableSymlink {
5151+ relative_package_dir: relative_package_dir.to_path_buf(),
5252+ subpath: subpath.to_path_buf(),
5353+ io_error,
7054 }
5555+ .into(),
7156 }
7257 } else if path.is_dir() {
7358 // Recursively check each entry
7474- for entry in utils::read_dir_sorted(&path)? {
7575- let entry_subpath = subpath.join(entry.file_name());
7676- check_path(context, &entry_subpath)
7777- .context(format!("Error while recursing into {}", subpath.display()))?
7878- }
5959+ validation::sequence_(
6060+ utils::read_dir_sorted(&path)?
6161+ .into_iter()
6262+ .map(|entry| {
6363+ let entry_subpath = subpath.join(entry.file_name());
6464+ check_path(relative_package_dir, absolute_package_dir, &entry_subpath)
6565+ .context(format!("Error while recursing into {}", subpath.display()))
6666+ })
6767+ .collect_vec()?,
6868+ )
7969 } else if path.is_file() {
8070 // Only check Nix files
8171 if let Some(ext) = path.extension() {
8272 if ext == OsStr::new("nix") {
8383- check_nix_file(context, subpath).context(format!(
8484- "Error while checking Nix file {}",
8585- subpath.display()
8686- ))?
7373+ check_nix_file(relative_package_dir, absolute_package_dir, subpath).context(
7474+ format!("Error while checking Nix file {}", subpath.display()),
7575+ )?
7676+ } else {
7777+ Success(())
8778 }
7979+ } else {
8080+ Success(())
8881 }
8982 } else {
9083 // This should never happen, git doesn't support other file types
9184 anyhow::bail!("Unsupported file type for path {}", subpath.display());
9292- }
9393- Ok(())
8585+ })
9486}
95879688/// Check whether a nix file contains path expression references pointing outside the package
9789/// directory
9898-fn check_nix_file<W: io::Write>(
9999- context: &mut PackageContext<W>,
9090+fn check_nix_file(
9191+ relative_package_dir: &Path,
9292+ absolute_package_dir: &Path,
10093 subpath: &Path,
101101-) -> anyhow::Result<()> {
102102- let path = context.absolute_package_dir.join(subpath);
9494+) -> validation::Result<()> {
9595+ let path = absolute_package_dir.join(subpath);
10396 let parent_dir = path.parent().context(format!(
10497 "Could not get parent of path {}",
10598 subpath.display()
···110103111104 let root = Root::parse(&contents);
112105 if let Some(error) = root.errors().first() {
113113- context.error_writer.write(&format!(
114114- "{}: File {} could not be parsed by rnix: {}",
115115- context.relative_package_dir.display(),
116116- subpath.display(),
117117- error,
118118- ))?;
119119- return Ok(());
106106+ return Ok(NixpkgsProblem::CouldNotParseNix {
107107+ relative_package_dir: relative_package_dir.to_path_buf(),
108108+ subpath: subpath.to_path_buf(),
109109+ error: error.clone(),
110110+ }
111111+ .into());
120112 }
121113122114 let line_index = LineIndex::new(&contents);
123115124124- for node in root.syntax().descendants() {
125125- // We're only interested in Path expressions
126126- if node.kind() != NODE_PATH {
127127- continue;
128128- }
116116+ Ok(validation::sequence_(root.syntax().descendants().map(
117117+ |node| {
118118+ let text = node.text().to_string();
119119+ let line = line_index.line(node.text_range().start().into());
129120130130- let text = node.text().to_string();
131131- let line = line_index.line(node.text_range().start().into());
132132-133133- // Filters out ./foo/${bar}/baz
134134- // TODO: We can just check ./foo
135135- if node.children().count() != 0 {
136136- context.error_writer.write(&format!(
137137- "{}: File {} at line {line} contains the path expression \"{}\", which is not yet supported and may point outside the directory of that package.",
138138- context.relative_package_dir.display(),
139139- subpath.display(),
140140- text
141141- ))?;
142142- continue;
143143- }
144144-145145- // Filters out search paths like <nixpkgs>
146146- if text.starts_with('<') {
147147- context.error_writer.write(&format!(
148148- "{}: File {} at line {line} contains the nix search path expression \"{}\" which may point outside the directory of that package.",
149149- context.relative_package_dir.display(),
150150- subpath.display(),
151151- text
152152- ))?;
153153- continue;
154154- }
155155-156156- // Resolves the reference of the Nix path
157157- // turning `../baz` inside `/foo/bar/default.nix` to `/foo/baz`
158158- match parent_dir.join(Path::new(&text)).canonicalize() {
159159- Ok(target) => {
160160- // Then checking if it's still in the package directory
161161- // No need to handle the case of it being inside the directory, since we scan through the
162162- // entire directory recursively anyways
163163- if let Err(_prefix_error) = target.strip_prefix(context.absolute_package_dir) {
164164- context.error_writer.write(&format!(
165165- "{}: File {} at line {line} contains the path expression \"{}\" which may point outside the directory of that package.",
166166- context.relative_package_dir.display(),
167167- subpath.display(),
168168- text,
169169- ))?;
121121+ if node.kind() != NODE_PATH {
122122+ // We're only interested in Path expressions
123123+ Success(())
124124+ } else if node.children().count() != 0 {
125125+ // Filters out ./foo/${bar}/baz
126126+ // TODO: We can just check ./foo
127127+ NixpkgsProblem::PathInterpolation {
128128+ relative_package_dir: relative_package_dir.to_path_buf(),
129129+ subpath: subpath.to_path_buf(),
130130+ line,
131131+ text,
170132 }
171171- }
172172- Err(e) => {
173173- context.error_writer.write(&format!(
174174- "{}: File {} at line {line} contains the path expression \"{}\" which cannot be resolved: {e}.",
175175- context.relative_package_dir.display(),
176176- subpath.display(),
133133+ .into()
134134+ } else if text.starts_with('<') {
135135+ // Filters out search paths like <nixpkgs>
136136+ NixpkgsProblem::SearchPath {
137137+ relative_package_dir: relative_package_dir.to_path_buf(),
138138+ subpath: subpath.to_path_buf(),
139139+ line,
177140 text,
178178- ))?;
141141+ }
142142+ .into()
143143+ } else {
144144+ // Resolves the reference of the Nix path
145145+ // turning `../baz` inside `/foo/bar/default.nix` to `/foo/baz`
146146+ match parent_dir.join(Path::new(&text)).canonicalize() {
147147+ Ok(target) => {
148148+ // Then checking if it's still in the package directory
149149+ // No need to handle the case of it being inside the directory, since we scan through the
150150+ // entire directory recursively anyways
151151+ if let Err(_prefix_error) = target.strip_prefix(absolute_package_dir) {
152152+ NixpkgsProblem::OutsidePathReference {
153153+ relative_package_dir: relative_package_dir.to_path_buf(),
154154+ subpath: subpath.to_path_buf(),
155155+ line,
156156+ text,
157157+ }
158158+ .into()
159159+ } else {
160160+ Success(())
161161+ }
162162+ }
163163+ Err(e) => NixpkgsProblem::UnresolvablePathReference {
164164+ relative_package_dir: relative_package_dir.to_path_buf(),
165165+ subpath: subpath.to_path_buf(),
166166+ line,
167167+ text,
168168+ io_error: e,
169169+ }
170170+ .into(),
171171+ }
179172 }
180180- };
181181- }
182182-183183- Ok(())
173173+ },
174174+ )))
184175}
+134-116
pkgs/test/nixpkgs-check-by-name/src/structure.rs
···11+use crate::nixpkgs_problem::NixpkgsProblem;
22+use crate::references;
13use crate::utils;
22-use crate::utils::ErrorWriter;
44+use crate::utils::{BASE_SUBPATH, PACKAGE_NIX_FILENAME};
55+use crate::validation::{self, ResultIteratorExt, Validation::Success};
66+use itertools::concat;
37use lazy_static::lazy_static;
48use regex::Regex;
55-use std::collections::HashMap;
66-use std::io;
99+use std::fs::DirEntry;
710use std::path::{Path, PathBuf};
88-99-pub const BASE_SUBPATH: &str = "pkgs/by-name";
1010-pub const PACKAGE_NIX_FILENAME: &str = "package.nix";
11111212lazy_static! {
1313 static ref SHARD_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_-]{1,2}$").unwrap();
1414 static ref PACKAGE_NAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap();
1515}
16161717-/// Contains information about the structure of the pkgs/by-name directory of a Nixpkgs
1818-pub struct Nixpkgs {
1919- /// The path to nixpkgs
2020- pub path: PathBuf,
2121- /// The names of all packages declared in pkgs/by-name
2222- pub package_names: Vec<String>,
1717+// Some utility functions for the basic structure
1818+1919+pub fn shard_for_package(package_name: &str) -> String {
2020+ package_name.to_lowercase().chars().take(2).collect()
2321}
24222525-impl Nixpkgs {
2626- // Some utility functions for the basic structure
2323+pub fn relative_dir_for_shard(shard_name: &str) -> PathBuf {
2424+ PathBuf::from(format!("{BASE_SUBPATH}/{shard_name}"))
2525+}
27262828- pub fn shard_for_package(package_name: &str) -> String {
2929- package_name.to_lowercase().chars().take(2).collect()
3030- }
2727+pub fn relative_dir_for_package(package_name: &str) -> PathBuf {
2828+ relative_dir_for_shard(&shard_for_package(package_name)).join(package_name)
2929+}
31303232- pub fn relative_dir_for_shard(shard_name: &str) -> PathBuf {
3333- PathBuf::from(format!("{BASE_SUBPATH}/{shard_name}"))
3434- }
3535-3636- pub fn relative_dir_for_package(package_name: &str) -> PathBuf {
3737- Nixpkgs::relative_dir_for_shard(&Nixpkgs::shard_for_package(package_name))
3838- .join(package_name)
3939- }
4040-4141- pub fn relative_file_for_package(package_name: &str) -> PathBuf {
4242- Nixpkgs::relative_dir_for_package(package_name).join(PACKAGE_NIX_FILENAME)
4343- }
3131+pub fn relative_file_for_package(package_name: &str) -> PathBuf {
3232+ relative_dir_for_package(package_name).join(PACKAGE_NIX_FILENAME)
4433}
45344646-impl Nixpkgs {
4747- /// Read the structure of a Nixpkgs directory, displaying errors on the writer.
4848- /// May return early with I/O errors.
4949- pub fn new<W: io::Write>(
5050- path: &Path,
5151- error_writer: &mut ErrorWriter<W>,
5252- ) -> anyhow::Result<Nixpkgs> {
5353- let base_dir = path.join(BASE_SUBPATH);
3535+/// Check the structure of Nixpkgs, returning the attribute names that are defined in
3636+/// `pkgs/by-name`
3737+pub fn check_structure(path: &Path) -> validation::Result<Vec<String>> {
3838+ let base_dir = path.join(BASE_SUBPATH);
54395555- let mut package_names = Vec::new();
5656-5757- for shard_entry in utils::read_dir_sorted(&base_dir)? {
4040+ let shard_results = utils::read_dir_sorted(&base_dir)?
4141+ .into_iter()
4242+ .map(|shard_entry| -> validation::Result<_> {
5843 let shard_path = shard_entry.path();
5944 let shard_name = shard_entry.file_name().to_string_lossy().into_owned();
6060- let relative_shard_path = Nixpkgs::relative_dir_for_shard(&shard_name);
4545+ let relative_shard_path = relative_dir_for_shard(&shard_name);
61466262- if shard_name == "README.md" {
4747+ Ok(if shard_name == "README.md" {
6348 // README.md is allowed to be a file and not checked
6464- continue;
6565- }
66496767- if !shard_path.is_dir() {
6868- error_writer.write(&format!(
6969- "{}: This is a file, but it should be a directory.",
7070- relative_shard_path.display(),
7171- ))?;
5050+ Success(vec![])
5151+ } else if !shard_path.is_dir() {
5252+ NixpkgsProblem::ShardNonDir {
5353+ relative_shard_path: relative_shard_path.clone(),
5454+ }
5555+ .into()
7256 // we can't check for any other errors if it's a file, since there's no subdirectories to check
7373- continue;
7474- }
5757+ } else {
5858+ let shard_name_valid = SHARD_NAME_REGEX.is_match(&shard_name);
5959+ let result = if !shard_name_valid {
6060+ NixpkgsProblem::InvalidShardName {
6161+ relative_shard_path: relative_shard_path.clone(),
6262+ shard_name: shard_name.clone(),
6363+ }
6464+ .into()
6565+ } else {
6666+ Success(())
6767+ };
75687676- let shard_name_valid = SHARD_NAME_REGEX.is_match(&shard_name);
7777- if !shard_name_valid {
7878- error_writer.write(&format!(
7979- "{}: Invalid directory name \"{shard_name}\", must be at most 2 ASCII characters consisting of a-z, 0-9, \"-\" or \"_\".",
8080- relative_shard_path.display()
8181- ))?;
8282- }
6969+ let entries = utils::read_dir_sorted(&shard_path)?;
83708484- let mut unique_package_names = HashMap::new();
7171+ let duplicate_results = entries
7272+ .iter()
7373+ .zip(entries.iter().skip(1))
7474+ .filter(|(l, r)| {
7575+ l.file_name().to_ascii_lowercase() == r.file_name().to_ascii_lowercase()
7676+ })
7777+ .map(|(l, r)| {
7878+ NixpkgsProblem::CaseSensitiveDuplicate {
7979+ relative_shard_path: relative_shard_path.clone(),
8080+ first: l.file_name(),
8181+ second: r.file_name(),
8282+ }
8383+ .into()
8484+ });
85858686- for package_entry in utils::read_dir_sorted(&shard_path)? {
8787- let package_path = package_entry.path();
8888- let package_name = package_entry.file_name().to_string_lossy().into_owned();
8989- let relative_package_dir =
9090- PathBuf::from(format!("{BASE_SUBPATH}/{shard_name}/{package_name}"));
8686+ let result = result.and(validation::sequence_(duplicate_results));
91879292- if !package_path.is_dir() {
9393- error_writer.write(&format!(
9494- "{}: This path is a file, but it should be a directory.",
9595- relative_package_dir.display(),
9696- ))?;
9797- continue;
9898- }
8888+ let package_results = entries
8989+ .into_iter()
9090+ .map(|package_entry| {
9191+ check_package(path, &shard_name, shard_name_valid, package_entry)
9292+ })
9393+ .collect_vec()?;
9994100100- if let Some(duplicate_package_name) =
101101- unique_package_names.insert(package_name.to_lowercase(), package_name.clone())
102102- {
103103- error_writer.write(&format!(
104104- "{}: Duplicate case-sensitive package directories \"{duplicate_package_name}\" and \"{package_name}\".",
105105- relative_shard_path.display(),
106106- ))?;
107107- }
9595+ result.and(validation::sequence(package_results))
9696+ })
9797+ })
9898+ .collect_vec()?;
10899109109- let package_name_valid = PACKAGE_NAME_REGEX.is_match(&package_name);
110110- if !package_name_valid {
111111- error_writer.write(&format!(
112112- "{}: Invalid package directory name \"{package_name}\", must be ASCII characters consisting of a-z, A-Z, 0-9, \"-\" or \"_\".",
113113- relative_package_dir.display(),
114114- ))?;
115115- }
100100+ // Combine the package names conatained within each shard into a longer list
101101+ Ok(validation::sequence(shard_results).map(concat))
102102+}
103103+104104+fn check_package(
105105+ path: &Path,
106106+ shard_name: &str,
107107+ shard_name_valid: bool,
108108+ package_entry: DirEntry,
109109+) -> validation::Result<String> {
110110+ let package_path = package_entry.path();
111111+ let package_name = package_entry.file_name().to_string_lossy().into_owned();
112112+ let relative_package_dir = PathBuf::from(format!("{BASE_SUBPATH}/{shard_name}/{package_name}"));
116113117117- let correct_relative_package_dir = Nixpkgs::relative_dir_for_package(&package_name);
118118- if relative_package_dir != correct_relative_package_dir {
119119- // Only show this error if we have a valid shard and package name
120120- // Because if one of those is wrong, you should fix that first
121121- if shard_name_valid && package_name_valid {
122122- error_writer.write(&format!(
123123- "{}: Incorrect directory location, should be {} instead.",
124124- relative_package_dir.display(),
125125- correct_relative_package_dir.display(),
126126- ))?;
127127- }
128128- }
114114+ Ok(if !package_path.is_dir() {
115115+ NixpkgsProblem::PackageNonDir {
116116+ relative_package_dir: relative_package_dir.clone(),
117117+ }
118118+ .into()
119119+ } else {
120120+ let package_name_valid = PACKAGE_NAME_REGEX.is_match(&package_name);
121121+ let result = if !package_name_valid {
122122+ NixpkgsProblem::InvalidPackageName {
123123+ relative_package_dir: relative_package_dir.clone(),
124124+ package_name: package_name.clone(),
125125+ }
126126+ .into()
127127+ } else {
128128+ Success(())
129129+ };
129130130130- let package_nix_path = package_path.join(PACKAGE_NIX_FILENAME);
131131- if !package_nix_path.exists() {
132132- error_writer.write(&format!(
133133- "{}: Missing required \"{PACKAGE_NIX_FILENAME}\" file.",
134134- relative_package_dir.display(),
135135- ))?;
136136- } else if package_nix_path.is_dir() {
137137- error_writer.write(&format!(
138138- "{}: \"{PACKAGE_NIX_FILENAME}\" must be a file.",
139139- relative_package_dir.display(),
140140- ))?;
131131+ let correct_relative_package_dir = relative_dir_for_package(&package_name);
132132+ let result = result.and(if relative_package_dir != correct_relative_package_dir {
133133+ // Only show this error if we have a valid shard and package name
134134+ // Because if one of those is wrong, you should fix that first
135135+ if shard_name_valid && package_name_valid {
136136+ NixpkgsProblem::IncorrectShard {
137137+ relative_package_dir: relative_package_dir.clone(),
138138+ correct_relative_package_dir: correct_relative_package_dir.clone(),
141139 }
140140+ .into()
141141+ } else {
142142+ Success(())
143143+ }
144144+ } else {
145145+ Success(())
146146+ });
142147143143- package_names.push(package_name.clone());
148148+ let package_nix_path = package_path.join(PACKAGE_NIX_FILENAME);
149149+ let result = result.and(if !package_nix_path.exists() {
150150+ NixpkgsProblem::PackageNixNonExistent {
151151+ relative_package_dir: relative_package_dir.clone(),
144152 }
145145- }
153153+ .into()
154154+ } else if package_nix_path.is_dir() {
155155+ NixpkgsProblem::PackageNixDir {
156156+ relative_package_dir: relative_package_dir.clone(),
157157+ }
158158+ .into()
159159+ } else {
160160+ Success(())
161161+ });
162162+163163+ let result = result.and(references::check_references(
164164+ &relative_package_dir,
165165+ &path.join(&relative_package_dir),
166166+ )?);
146167147147- Ok(Nixpkgs {
148148- path: path.to_owned(),
149149- package_names,
150150- })
151151- }
168168+ result.map(|_| package_name.clone())
169169+ })
152170}
+3-24
pkgs/test/nixpkgs-check-by-name/src/utils.rs
···11use anyhow::Context;
22-use colored::Colorize;
32use std::fs;
43use std::io;
54use std::path::Path;
55+66+pub const BASE_SUBPATH: &str = "pkgs/by-name";
77+pub const PACKAGE_NIX_FILENAME: &str = "package.nix";
6879/// Deterministic file listing so that tests are reproducible
810pub fn read_dir_sorted(base_dir: &Path) -> anyhow::Result<Vec<fs::DirEntry>> {
···4749 }
4850 }
4951}
5050-5151-/// A small wrapper around a generic io::Write specifically for errors:
5252-/// - Print everything in red to signal it's an error
5353-/// - Keep track of whether anything was printed at all, so that
5454-/// it can be queried whether any errors were encountered at all
5555-pub struct ErrorWriter<W> {
5656- pub writer: W,
5757- pub empty: bool,
5858-}
5959-6060-impl<W: io::Write> ErrorWriter<W> {
6161- pub fn new(writer: W) -> ErrorWriter<W> {
6262- ErrorWriter {
6363- writer,
6464- empty: true,
6565- }
6666- }
6767-6868- pub fn write(&mut self, string: &str) -> io::Result<()> {
6969- self.empty = false;
7070- writeln!(self.writer, "{}", string.red())
7171- }
7272-}
+102
pkgs/test/nixpkgs-check-by-name/src/validation.rs
···11+use crate::nixpkgs_problem::NixpkgsProblem;
22+use itertools::concat;
33+use itertools::{
44+ Either::{Left, Right},
55+ Itertools,
66+};
77+use Validation::*;
88+99+/// The validation result of a check.
1010+/// Instead of exiting at the first failure,
1111+/// this type can accumulate multiple failures.
1212+/// This can be achieved using the functions `and`, `sequence` and `sequence_`
1313+///
1414+/// This leans on https://hackage.haskell.org/package/validation
1515+pub enum Validation<A> {
1616+ Failure(Vec<NixpkgsProblem>),
1717+ Success(A),
1818+}
1919+2020+impl<A> From<NixpkgsProblem> for Validation<A> {
2121+ /// Create a `Validation<A>` from a single check problem
2222+ fn from(value: NixpkgsProblem) -> Self {
2323+ Failure(vec![value])
2424+ }
2525+}
2626+2727+/// A type alias representing the result of a check, either:
2828+/// - Err(anyhow::Error): A fatal failure, typically I/O errors.
2929+/// Such failures are not caused by the files in Nixpkgs.
3030+/// This hints at a bug in the code or a problem with the deployment.
3131+/// - Ok(Failure(Vec<NixpkgsProblem>)): A non-fatal validation problem with the Nixpkgs files.
3232+/// Further checks can be run even with this result type.
3333+/// Such problems can be fixed by changing the Nixpkgs files.
3434+/// - Ok(Success(A)): A successful (potentially intermediate) result with an arbitrary value.
3535+/// No fatal errors have occurred and no validation problems have been found with Nixpkgs.
3636+pub type Result<A> = anyhow::Result<Validation<A>>;
3737+3838+pub trait ResultIteratorExt<A, E>: Sized + Iterator<Item = std::result::Result<A, E>> {
3939+ fn collect_vec(self) -> std::result::Result<Vec<A>, E>;
4040+}
4141+4242+impl<I, A, E> ResultIteratorExt<A, E> for I
4343+where
4444+ I: Sized + Iterator<Item = std::result::Result<A, E>>,
4545+{
4646+ /// A convenience version of `collect` specialised to a vector
4747+ fn collect_vec(self) -> std::result::Result<Vec<A>, E> {
4848+ self.collect()
4949+ }
5050+}
5151+5252+impl<A> Validation<A> {
5353+ /// Map a `Validation<A>` to a `Validation<B>` by applying a function to the
5454+ /// potentially contained value in case of success.
5555+ pub fn map<B>(self, f: impl FnOnce(A) -> B) -> Validation<B> {
5656+ match self {
5757+ Failure(err) => Failure(err),
5858+ Success(value) => Success(f(value)),
5959+ }
6060+ }
6161+}
6262+6363+impl Validation<()> {
6464+ /// Combine two validations, both of which need to be successful for the return value to be successful.
6565+ /// The `NixpkgsProblem`s of both sides are returned concatenated.
6666+ pub fn and<A>(self, other: Validation<A>) -> Validation<A> {
6767+ match (self, other) {
6868+ (Success(_), Success(right_value)) => Success(right_value),
6969+ (Failure(errors), Success(_)) => Failure(errors),
7070+ (Success(_), Failure(errors)) => Failure(errors),
7171+ (Failure(errors_l), Failure(errors_r)) => Failure(concat([errors_l, errors_r])),
7272+ }
7373+ }
7474+}
7575+7676+/// Combine many validations into a single one.
7777+/// All given validations need to be successful in order for the returned validation to be successful,
7878+/// in which case the returned validation value contains a `Vec` of each individual value.
7979+/// Otherwise the `NixpkgsProblem`s of all validations are returned concatenated.
8080+pub fn sequence<A>(check_results: impl IntoIterator<Item = Validation<A>>) -> Validation<Vec<A>> {
8181+ let (errors, values): (Vec<Vec<NixpkgsProblem>>, Vec<A>) = check_results
8282+ .into_iter()
8383+ .partition_map(|validation| match validation {
8484+ Failure(err) => Left(err),
8585+ Success(value) => Right(value),
8686+ });
8787+8888+ // To combine the errors from the results we flatten all the error Vec's into a new Vec
8989+ // This is not very efficient, but doesn't matter because generally we should have no errors
9090+ let flattened_errors = errors.into_iter().concat();
9191+9292+ if flattened_errors.is_empty() {
9393+ Success(values)
9494+ } else {
9595+ Failure(flattened_errors)
9696+ }
9797+}
9898+9999+/// Like `sequence`, but without any containing value, for convenience
100100+pub fn sequence_(validations: impl IntoIterator<Item = Validation<()>>) -> Validation<()> {
101101+ sequence(validations).map(|_| ())
102102+}
+56-1
pkgs/tools/admin/pgadmin/default.nix
···99, yarn
1010, fixup_yarn_lock
1111, nodejs
1212+, fetchpatch
1213, server-mode ? true
1314}:
1415···26272728 # keep the scope, as it is used throughout the derivation and tests
2829 # this also makes potential future overrides easier
2929- pythonPackages = python3.pkgs.overrideScope (final: prev: rec { });
3030+ pythonPackages = python3.pkgs.overrideScope (final: prev: rec {
3131+ # pgadmin 7.8 is incompatible with Flask >= 2.3
3232+ flask = prev.flask.overridePythonAttrs (oldAttrs: rec {
3333+ version = "2.2.5";
3434+ src = oldAttrs.src.override {
3535+ pname = "Flask";
3636+ inherit version;
3737+ hash = "sha256-7e6bCn/yZiG9WowQ/0hK4oc3okENmbC7mmhQx/uXeqA=";
3838+ };
3939+ format = "setuptools";
4040+ });
4141+ # downgrade needed for older Flask
4242+ httpbin = prev.httpbin.overridePythonAttrs (oldAttrs: rec {
4343+ version = "0.7.0";
4444+ src = oldAttrs.src.override {
4545+ inherit version;
4646+ hash = "sha256-y7N3kMkVdfTxV1f0KtQdn3KesifV7b6J5OwXVIbbjfo=";
4747+ };
4848+ format = "setuptools";
4949+ patches = [
5050+ (fetchpatch {
5151+ # Replaces BaseResponse class with Response class for Werkezug 2.1.0 compatibility
5252+ # https://github.com/postmanlabs/httpbin/pull/674
5353+ url = "https://github.com/postmanlabs/httpbin/commit/5cc81ce87a3c447a127e4a1a707faf9f3b1c9b6b.patch";
5454+ hash = "sha256-SbEWjiqayMFYrbgAPZtSsXqSyCDUz3z127XgcKOcrkE=";
5555+ })
5656+ ];
5757+ pytestFlagsArray = [
5858+ "test_httpbin.py"
5959+ ];
6060+ propagatedBuildInputs = oldAttrs.propagatedBuildInputs ++ [ final.pythonPackages.brotlipy ];
6161+ });
6262+ # downgrade needed for older httpbin
6363+ werkzeug = prev.werkzeug.overridePythonAttrs (oldAttrs: rec {
6464+ version = "2.2.3";
6565+ format = "setuptools";
6666+ src = oldAttrs.src.override {
6767+ pname = "Werkzeug";
6868+ inherit version;
6969+ hash = "sha256-LhzMlBfU2jWLnebxdOOsCUOR6h1PvvLWZ4ZdgZ39Cv4=";
7070+ };
7171+ });
7272+ # Downgrade needed for older Flask
7373+ flask-security-too = prev.flask-security-too.overridePythonAttrs (oldAttrs: rec {
7474+ version = "5.1.2";
7575+ src = oldAttrs.src.override {
7676+ inherit version;
7777+ hash = "sha256-lZzm43m30y+2qjxNddFEeg9HDlQP9afq5VtuR25zaLc=";
7878+ };
7979+ postPatch = ''
8080+ # This should be removed after updating to version 5.3.0.
8181+ sed -i '/filterwarnings =/a ignore:pkg_resources is deprecated:DeprecationWarning' pytest.ini
8282+ '';
8383+ });
8484+ });
30853186 offlineCache = fetchYarnDeps {
3287 yarnLock = ./yarn.lock;
···178178 ];
179179 };
180180181181+ linux_6_6 = callPackage ../os-specific/linux/kernel/mainline.nix {
182182+ branch = "6.6";
183183+ kernelPatches = [
184184+ kernelPatches.bridge_stp_helper
185185+ kernelPatches.request_key_helper
186186+ ];
187187+ };
188188+181189 linux_testing = let
182190 testing = callPackage ../os-specific/linux/kernel/mainline.nix {
183191 # A special branch that tracks the kernel under the release process
···567575 linux_5_15 = recurseIntoAttrs (packagesFor kernels.linux_5_15);
568576 linux_6_1 = recurseIntoAttrs (packagesFor kernels.linux_6_1);
569577 linux_6_5 = recurseIntoAttrs (packagesFor kernels.linux_6_5);
578578+ linux_6_6 = recurseIntoAttrs (packagesFor kernels.linux_6_6);
570579 } // lib.optionalAttrs config.allowAliases {
571580 linux_4_9 = throw "linux 4.9 was removed because it will reach its end of life within 22.11"; # Added 2022-11-08
572581 linux_4_14 = throw "linux 4.14 was removed because it will reach its end of life within 23.11"; # Added 2023-10-11
···627636 packageAliases = {
628637 linux_default = packages.linux_6_1;
629638 # Update this when adding the newest kernel major version!
630630- linux_latest = packages.linux_6_5;
639639+ linux_latest = packages.linux_6_6;
631640 linux_mptcp = throw "'linux_mptcp' has been moved to https://github.com/teto/mptcp-flake";
632641 linux_rt_default = packages.linux_rt_5_4;
633642 linux_rt_latest = packages.linux_rt_6_1;