Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions doc/redirects.json
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,9 @@
"chap-stdenv": [
"index.html#chap-stdenv"
],
"sec-problems": [
"index.html#sec-problems"
],
"sec-using-llvm": [
"index.html#sec-using-llvm"
],
Expand Down
53 changes: 53 additions & 0 deletions doc/using/configuration.chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ By default, Nix will prevent installation if any of the following criteria are t

- The package has known security vulnerabilities but has not or can not be updated for some reason, and a list of issues has been entered into the package's `meta.knownVulnerabilities`.

- There are problems for packages which must be acknowledged, e.g. deprecation notices.

Each of these criteria can be altered in the Nixpkgs configuration.

:::{.note}
Expand Down Expand Up @@ -166,6 +168,57 @@ There are several ways to tweak how Nix handles a package which has been marked

Note that `permittedInsecurePackages` is only checked if `allowInsecurePredicate` is not specified.

## Packages with problems {#sec-problems}

A package may have several problems associated with it.
These can be either manually declared in `meta.problems`, or automatically generated from its other `meta` attributes.
Each problem has a name, a "kind", a message, and optionally a list of URLs.
Not all kinds can be manually specified in `meta.problems`, and some kinds can exist only up to once per package.
Currently, the following problem kinds are known (with more reserved to be added in the future):

- "removal": The package is planned to be removed some time in the future. Unique.
- "deprecated": The package relies on software which has reached its end of life.
- "maintainerless": Automatically generated for packages with `meta.maintainers == []`. Unique, not manually specifiable.

Each problem has a handler that deals with it, which can be one of "error", "warn" or "ignore".
"error" will disallow evaluating a package, while "warn" will simply print a message to the log.

The handler for problems can be specified using `config.problems.handlers.${packageName}.${problemName} = "${handler}";`.

There is also the possibility to specify some generic matchers, which can set a handler for more than a specific problem of a specific package.
This works through the `config.problems.matchers` option:

```nix
{
problems.matchers = [
# Fail to build any packages which are about to be removed anyway
{
kind = "removal";
handler = "error";
}

# Get warnings when using packages with no declared maintainers
{
kind = "maintainerless";
handler = "warn";
}

# You deeply care about this package and want to absolutely know when it has any problems
{
package = "hello";
handler = "error";
}
];
}
```

Matchers can match one or more of package name, problem name or problem kind.
If multiple conditions are present, all must be met to match.
If multiple matchers match a problem, then the highest severity handler will be chosen.
The current default value contains `{ kind = "removal"; handler = "error"; }`, meaning that people will be notified about package removals in advance.

Package names for both `problems.handlers` and `problems.matchers` are taken from `lib.getName`, which looks at the `pname` first and falls back to extracting the "pname" part from the `name` attribute.

## Modify packages via `packageOverrides` {#sec-modify-via-packageOverrides}

You can define a function called `packageOverrides` in your local `~/.config/nixpkgs/config.nix` to override Nix packages. It must be a function that takes pkgs as an argument and returns a modified set of packages.
Expand Down
179 changes: 79 additions & 100 deletions pkgs/stdenv/generic/check-meta.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
let
inherit (lib)
all
attrNames
attrValues
concatMapStrings
concatMapStringsSep
concatStrings
filter
findFirst
Expand All @@ -26,7 +24,8 @@ let
optionalString
isAttrs
isString
mapAttrs
warn
foldl'
;

inherit (lib.lists)
Expand All @@ -49,15 +48,17 @@ let

inherit (builtins)
getEnv
trace
;

inherit (import ./problems.nix { inherit lib; })
problemsType
genCheckProblems
;
checkProblems = genCheckProblems config;

# If we're in hydra, we can dispense with the more verbose error
# messages and make problems easier to spot.
inHydra = config.inHydra or false;
# Allow the user to opt-into additional warnings, e.g.
# import <nixpkgs> { config = { showDerivationWarnings = [ "maintainerless" ]; }; }
showWarnings = config.showDerivationWarnings;

getNameWithVersion =
attrs: attrs.name or "${attrs.pname or "«name-missing»"}-${attrs.version or "«version-missing»"}";
Expand Down Expand Up @@ -118,21 +119,6 @@ let

hasUnfreeLicense = attrs: attrs ? meta.license && isUnfree attrs.meta.license;

hasNoMaintainers =
# To get usable output, we want to avoid flagging "internal" derivations.
# Because we do not have a way to reliably decide between internal or
# external derivation, some heuristics are required to decide.
#
# If `outputHash` is defined, the derivation is a FOD, such as the output of a fetcher.
# If `description` is not defined, the derivation is probably not a package.
# Simply checking whether `meta` is defined is insufficient,
# as some fetchers and trivial builders do define meta.
attrs:
(!attrs ? outputHash)
&& (attrs ? meta.description)
&& (attrs.meta.maintainers or [ ] == [ ])
&& (attrs.meta.teams or [ ] == [ ]);

isMarkedBroken = attrs: attrs.meta.broken or false;

# Allow granular checks to allow only some broken packages
Expand Down Expand Up @@ -305,7 +291,7 @@ let
${concatStrings (map (output: " - ${output}\n") missingOutputs)}
'';

metaTypes =
metaType =
let
types = import ./meta-types.nix { inherit lib; };
inherit (types)
Expand All @@ -317,13 +303,14 @@ let
any
listOf
bool
record
;
platforms = listOf (union [
str
(attrsOf any)
]); # see lib.meta.platformMatch
in
{
record {
# These keys are documented
description = str;
mainProgram = str;
Expand Down Expand Up @@ -361,6 +348,8 @@ let
unfree = bool;
unsupported = bool;
insecure = bool;
# This is checked in more detail further down
problems = problemsType;
tests = {
name = "test";
verify =
Expand Down Expand Up @@ -400,34 +389,7 @@ let
identifiers = attrs;
};

# Map attrs directly to the verify function for performance
metaTypes' = mapAttrs (_: t: t.verify) metaTypes;

checkMetaAttr =
k: v:
if metaTypes ? ${k} then
if metaTypes'.${k} v then
[ ]
else
[
"key 'meta.${k}' has invalid value; expected ${metaTypes.${k}.name}, got\n ${
toPretty { indent = " "; } v
}"
]
else
[
"key 'meta.${k}' is unrecognized; expected one of: \n [${
concatMapStringsSep ", " (x: "'${x}'") (attrNames metaTypes)
}]"
];

checkMeta = meta: concatMap (attr: checkMetaAttr attr meta.${attr}) (attrNames meta);

metaInvalid =
if config.checkMeta then
meta: !all (attr: metaTypes ? ${attr} && metaTypes'.${attr} meta.${attr}) (attrNames meta)
else
meta: false;
metaInvalid = if config.checkMeta then meta: !metaType.verify meta else meta: false;

checkOutputsToInstall =
if config.checkMeta then
Expand Down Expand Up @@ -455,7 +417,7 @@ let
{
reason = "unknown-meta";
errormsg = "has an invalid meta attrset:${
concatMapStrings (x: "\n - " + x) (checkMeta attrs.meta)
concatMapStrings (x: "\n - " + x) (metaType.errors "${getName attrs}.meta" attrs.meta)
}\n";
remediation = "";
}
Expand Down Expand Up @@ -519,18 +481,6 @@ let
else
null;

# Please also update the type in /pkgs/top-level/config.nix alongside this.
checkWarnings =
attrs:
if hasNoMaintainers attrs then
{
reason = "maintainerless";
errormsg = "has no maintainers or teams";
remediation = "";
}
else
null;

# Helper functions and declarations to handle identifiers, extracted to reduce allocations
hasAllCPEParts =
cpeParts:
Expand Down Expand Up @@ -682,59 +632,88 @@ let
unsupported = hasUnsupportedPlatform attrs;
insecure = isMarkedInsecure attrs;

# TODO: We can't really do this, because then a meta value can't be reused as an input
# Maybe we can just ignore any fields that are automatically set instead of disallowing them
#problems = processProblems attrs;

available =
validity.valid != "no"
&& ((config.checkMetaRecursively or false) -> all (d: d.meta.available or true) references);
};

validYes = {
valid = "yes";
handled = true;
};

assertValidity =
{ meta, attrs }:
handle =
{
attrs,
meta,
warnings ? [ ],
error ? null,
}:
let
invalid = checkValidity attrs;
warning = checkWarnings attrs;
in
if isNull invalid then
if isNull warning then
validYes
else
withError =
if isNull error then
true
else
let
msg =
if inHydra then
"Failed to evaluate ${getNameWithVersion attrs}: «${error.reason}»: ${error.errormsg}"
else
''
Package ‘${getNameWithVersion attrs}’ in ${pos_str meta} ${error.errormsg}, refusing to evaluate.

''
+ error.remediation;
in
if config ? handleEvalIssue then config.handleEvalIssue error.reason msg else throw msg;

# TODO: Mention remediation
giveWarning =
acc: warning:
let
msg =
if inHydra then
"Warning while evaluating ${getNameWithVersion attrs}: «${warning.reason}»: ${warning.errormsg}"
else
"Package ${getNameWithVersion attrs} in ${pos_str meta} ${warning.errormsg}, continuing anyway."
"Package ${getNameWithVersion attrs} in ${pos_str meta} ${warning.errormsg} Continuing anyway."
+ (optionalString (warning.remediation != "") "\n${warning.remediation}");

handled = if elem warning.reason showWarnings then trace msg true else true;
in
warning
// {
valid = "warn";
handled = handled;
warn msg acc;
in
# Give all warnings first, then error if any
builtins.seq (foldl' giveWarning null warnings) withError;

assertValidity =
{ meta, attrs }:
let
invalid = checkValidity attrs;
problems = checkProblems attrs;
in
if isNull invalid then
if isNull problems then
{
valid = "yes";
handled = true;
}
else
# Are these really needed in the output?
#reason = "problems";
#errormsg = "";
#remediation = "";
{
valid = if isNull problems.error then "warn" else "no";
handled = handle {
inherit attrs meta;
inherit (problems) error warnings;
};
}
else
let
msg =
if inHydra then
"Failed to evaluate ${getNameWithVersion attrs}: «${invalid.reason}»: ${invalid.errormsg}"
else
''
Package ‘${getNameWithVersion attrs}’ in ${pos_str meta} ${invalid.errormsg}, refusing to evaluate.

''
+ invalid.remediation;

handled = if config ? handleEvalIssue then config.handleEvalIssue invalid.reason msg else throw msg;
in
invalid
// {
valid = "no";
handled = handled;
handled = handle {
inherit attrs meta;
error = invalid;
};
};

in
Expand Down
Loading
Loading