[Backport release-26.05] stdenv/problems: only run problem if it's not ignored (#535856)

This commit is contained in:
Matt Sturgeon 2026-06-26 23:29:07 +00:00 committed by GitHub
commit 43e856aed1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 148 additions and 35 deletions

View file

@ -48,6 +48,8 @@ rec {
groupBy
subtractLists
genAttrs
concatMap
unique
;
handlers = rec {
@ -119,14 +121,16 @@ rec {
allowBroken = config.allowBroken || builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1";
allowBrokenPredicate =
if config ? allowBrokenPredicate then
lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2605)
"config.allowBrokenPredicate is deprecated, use config.problems.handlers.myPackage.broken = \"warn\" for individual packages instead."
config.allowBrokenPredicate
else
x: false;
lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2605)
"config.allowBrokenPredicate is deprecated, use config.problems.handlers.myPackage.broken = \"warn\" for individual packages instead."
config.allowBrokenPredicate;
in
attrs: attrs.meta.broken or false && !allowBroken && !allowBrokenPredicate attrs;
if allowBroken then
attrs: false
else if config ? allowBrokenPredicate then
attrs: attrs ? meta.broken && attrs.meta.broken && !allowBrokenPredicate attrs
else
attrs: attrs ? meta.broken && attrs.meta.broken;
value.message = "This package is broken.";
}
];
@ -330,7 +334,10 @@ rec {
};
};
Returns both the structure itself for inspection and a function that can query it with very few allocations/lookups
Returns:
- the structure itself for inspection
- a function that can query the structure with very few allocations/lookups
- a list of problem kinds/names/packages that require handling
This allows collapsing arbitrarily many problem handlers/matchers into a predictable structure that can be queried in a predictable and fast way
*/
@ -352,6 +359,20 @@ rec {
) config.problems.handlers
);
# Lookup table for all the kinds/names/packages that actually need to be
# handled
definedConstraints = listToAttrs (
map (ident: {
name = "${ident}s"; # plural
value = unique (
concatMap (
constraint:
optionals (constraint.${ident} != null && constraint.handler != "ignore") [ (constraint.${ident}) ]
) constraints
);
}) identOrder
);
getHandler =
list:
(foldl'
@ -410,26 +431,27 @@ rec {
switch = doLevel 0 constraints;
in
{
inherit switch;
inherit switch definedConstraints;
handlerForProblem =
if isString switch then
pname: name: kind:
kind: name: pname:
switch
else
pname: name: kind:
kind:
let
switch' = switch.kindSpecific.${kind} or switch.kindFallback;
kindSwitch = switch.kindSpecific.${kind} or switch.kindFallback;
in
if isString switch' then
switch'
if isString kindSwitch then
name: pname: kindSwitch
else
name:
let
switch'' = switch'.nameSpecific.${name} or switch'.nameFallback;
nameSwitch = kindSwitch.nameSpecific.${name} or kindSwitch.nameFallback;
in
if isString switch'' then
switch''
if isString nameSwitch then
pname: nameSwitch
else
switch''.packageSpecific.${pname} or switch''.packageFallback;
pname: nameSwitch.packageSpecific.${pname} or nameSwitch.packageFallback;
};
genCheckProblems =
@ -438,44 +460,50 @@ rec {
# This is here so that it gets cached for a (checkProblems config) thunk
inherit (genHandlerSwitch config)
handlerForProblem
definedConstraints
;
# Makes sure that automatic problems can cache with just config applied
automaticProblemsConfigCache = map (
problem: problem // { condition = problem.condition config; }
# All the problem kinds that actually need to be checked
configuredProblems = definedConstraints.kinds ++ definedConstraints.names;
# Filter out any problems that are always ignored in config.problems.
# Makes sure to cache the condition by appliny config, and the handler
# by applying the problem's kind and name
automaticProblemsConfigCache = concatMap (
problem:
optional (elem problem.kindName configuredProblems) {
condition = problem.condition config;
handler = handlerForProblem problem.kindName problem.kindName;
}
) automaticProblems;
in
attrs:
let
pname = getName attrs;
manualProblems = attrs.meta.problems or { };
in
if
# Fast path for when there's no problem that needs to be handled
# No automatic problems that needs handling
all (
problem:
problem.condition attrs -> handlerForProblem pname problem.kindName problem.kindName == "ignore"
problem: problem.condition attrs -> problem.handler (getName attrs) == "ignore"
) automaticProblemsConfigCache
&& (
# No manual problems
manualProblems == { }
!attrs ? meta.problems
# Or all manual problems are ignored
|| all (name: handlerForProblem pname name (manualProblems.${name}.kind or name) == "ignore") (
attrNames manualProblems
)
|| all (
name: handlerForProblem (attrs.meta.problems.${name}.kind or name) name (getName attrs) == "ignore"
) (attrNames attrs.meta.problems)
)
then
null
else
# Slow path, only here we actually figure out which problems we need to handle
let
pname = getName attrs;
problems = attrs.meta.problems or { } // genAutomaticProblems config attrs;
problemsToHandle = filter (v: v.handler != "ignore") (
mapAttrsToList (name: problem: rec {
inherit name;
# Kind falls back to the name
kind = problem.kind or name;
handler = handlerForProblem pname name kind;
handler = handlerForProblem kind name pname;
inherit problem;
}) problems
);

View file

@ -2,10 +2,15 @@
let
p = import ../../stdenv/generic/problems.nix { inherit lib; };
genConstraintsTest = problems: expected: {
expr = (p.genHandlerSwitch { inherit problems; }).definedConstraints;
inherit expected;
};
genHandlerTest =
let
slowReference =
config: package: name: kind:
config: kind: name: package:
# Try to find an explicit handler
(config.problems.handlers.${package} or { }).${name}
# Fall back, iterating through the matchers
@ -31,7 +36,7 @@ let
map
(
name:
map (kind: f package name kind) [
map (kind: f kind name package) [
"k1"
"k2"
"k3"
@ -150,4 +155,84 @@ lib.runTests {
];
handlers = { };
};
testDefinedConstraintsEmpty =
genConstraintsTest
{
matchers = [ ];
handlers = { };
}
{
kinds = [ ];
names = [ ];
packages = [ ];
};
testDefinedConstraintsMatchers =
genConstraintsTest
{
handlers = { };
matchers = [
{
package = null;
name = null;
kind = "k1";
handler = "warn";
}
{
package = null;
name = null;
kind = "k2";
handler = "error";
}
{
package = null;
name = null;
kind = "k3";
handler = "ignore";
}
{
package = "p1";
name = "n1";
kind = null;
handler = "error";
}
{
package = "p2";
name = "n1";
kind = null;
handler = "warn";
}
];
}
{
kinds = [
"k1"
"k2"
];
names = [ "n1" ];
packages = [
"p1"
"p2"
];
};
testDefinedConstraintsHandlers =
genConstraintsTest
{
matchers = [ ];
handlers.p1.n1 = "warn";
handlers.p1.n2 = "error";
handlers.p2.n3 = "ignore";
}
{
kinds = [ ];
names = [
"n1"
"n2"
];
packages = [
"p1"
];
};
}