[Backport release-26.05] Allow meta.problems while preventing internal use of certain kinds (#535859)

This commit is contained in:
Matt Sturgeon 2026-06-26 23:48:40 +00:00 committed by GitHub
commit 3a45106ee8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 333 additions and 171 deletions

View file

@ -1,85 +0,0 @@
# This expression will, as efficiently as possible, dump a
# *superset* of all attrpaths of derivations which might be
# part of a release on *any* platform.
#
# This expression runs single-threaded under all current Nix
# implementations, but much faster and with much less memory
# used than ./outpaths.nix itself.
#
# Once you have the list of attrnames you can split it up into
# $NUM_CORES batches and evaluate the outpaths separately for each
# batch, in parallel.
#
# To dump the attrnames:
#
# nix-instantiate --eval --strict --json ci/eval/attrpaths.nix -A names
#
{
lib ? import (path + "/lib"),
trace ? false,
path ? ./../..,
extraNixpkgsConfigJson ? "{}",
}:
let
# TODO: Use mapAttrsToListRecursiveCond when this PR lands:
# https://github.com/NixOS/nixpkgs/pull/395160
justAttrNames =
path: value:
let
result =
if path == [ "AAAAAASomeThingsFailToEvaluate" ] || !(lib.isAttrs value) then
[ ]
else if lib.isDerivation value then
[ path ]
else
lib.pipe value [
(lib.mapAttrsToList (
name: value:
lib.addErrorContext "while evaluating package set attribute path '${
lib.showAttrPath (path ++ [ name ])
}'" (justAttrNames (path ++ [ name ]) value)
))
lib.concatLists
];
in
lib.traceIf trace "** ${lib.showAttrPath path}" result;
outpaths = import ./outpaths.nix {
inherit path;
extraNixpkgsConfig = builtins.fromJSON extraNixpkgsConfigJson;
attrNamesOnly = true;
};
paths = [
# Some of the following are based on variants, which are disabled with `attrNamesOnly = true`.
# Until these have been removed from release.nix / hydra, we manually add them to the list.
[
"pkgsLLVM"
"stdenv"
]
[
"pkgsArocc"
"stdenv"
]
[
"pkgsZig"
"stdenv"
]
[
"pkgsStatic"
"stdenv"
]
[
"pkgsMusl"
"stdenv"
]
]
++ justAttrNames [ ] outpaths;
names = map lib.showAttrPath paths;
in
{
inherit paths names;
}

View file

@ -2,8 +2,8 @@
{
lib ? import ../../lib,
path ? ../..,
# The file containing all available attribute paths, which are split into chunks here
attrpathFile,
# The file containing the preEval result
preEvalFile,
chunkSize,
myChunk,
includeBroken,
@ -12,12 +12,13 @@
}:
let
attrpaths = lib.importJSON attrpathFile;
myAttrpaths = lib.sublist (chunkSize * myChunk) chunkSize attrpaths;
preEvalResult = lib.importJSON preEvalFile;
myAttrpaths = lib.sublist (chunkSize * myChunk) chunkSize preEvalResult.paths;
unfiltered = import ./outpaths.nix {
inherit path;
inherit includeBroken systems;
inherit (preEvalResult) attrPathsDisallowedForInternalUse;
extraNixpkgsConfig = builtins.fromJSON extraNixpkgsConfigJson;
};

View file

@ -38,7 +38,7 @@ let
fileset = unions (
map (lib.path.append ../..) [
".version"
"ci/eval/attrpaths.nix"
"ci/eval/pre-eval.nix"
"ci/eval/chunk.nix"
"ci/eval/outpaths.nix"
"default.nix"
@ -56,11 +56,11 @@ let
builtins.readFile ../../pkgs/top-level/release-supported-systems.json
);
attrpathsSuperset =
preEval =
{
evalSystem,
}:
runCommand "attrpaths-superset.json"
runCommand "pre-eval"
{
src = nixpkgs;
# Don't depend on -dev outputs to reduce closure size for CI.
@ -73,15 +73,15 @@ let
export NIX_STATE_DIR=$(mktemp -d)
mkdir $out
export GC_INITIAL_HEAP_SIZE=4g
command time -f "Attribute eval done [%MKB max resident, %Es elapsed] %C" \
command time -f "Pre-eval done [%MKB max resident, %Es elapsed] %C" \
nix-instantiate --eval --strict --json --show-trace \
"$src/ci/eval/attrpaths.nix" \
-A paths \
"$src/ci/eval/pre-eval.nix" \
-A result \
-I "$src" \
--argstr extraNixpkgsConfigJson ${lib.escapeShellArg (builtins.toJSON extraNixpkgsConfig)} \
--option restrict-eval true \
--option allow-import-from-derivation false \
--option eval-system "${evalSystem}" > $out/paths.json
--option eval-system "${evalSystem}" > $out/result.json
'';
singleSystem =
@ -90,8 +90,8 @@ let
# Note that this is intentionally not called `system`,
# because `--argstr system` would only be passed to the ci/default.nix file!
evalSystem ? builtins.currentSystem,
# The path to the `paths.json` file from `attrpathsSuperset`
attrpathFile ? "${attrpathsSuperset { inherit evalSystem; }}/paths.json",
# The path to the `result.json` file from `preEval`
preEvalFile ? "${preEval { inherit evalSystem; }}/result.json",
}:
let
singleChunk = writeShellScript "single-chunk" ''
@ -121,12 +121,12 @@ let
--show-trace \
--arg chunkSize "$chunkSize" \
--arg myChunk "$myChunk" \
--arg attrpathFile "${attrpathFile}" \
--arg preEvalFile "${preEvalFile}" \
--arg systems "[ \"$system\" ]" \
--arg includeBroken ${lib.boolToString includeBroken} \
--argstr extraNixpkgsConfigJson ${lib.escapeShellArg (builtins.toJSON extraNixpkgsConfig)} \
-I ${nixpkgs} \
-I ${attrpathFile} \
-I ${preEvalFile} \
> "$outputDir/result/$myChunk" \
2> "$outputDir/stderr/$myChunk"
exitCode=$?
@ -164,7 +164,7 @@ let
echo "System: $evalSystem"
cores=$NIX_BUILD_CORES
echo "Cores: $cores"
attrCount=$(jq length "${attrpathFile}")
attrCount=$(jq '.paths | length' "${preEvalFile}")
echo "Attribute count: $attrCount"
echo "Chunk size: $chunkSize"
# Same as `attrCount / chunkSize` but rounded up
@ -316,7 +316,7 @@ let
in
{
inherit
attrpathsSuperset
preEval
singleSystem
diff
combine

View file

@ -6,7 +6,7 @@
includeBroken ? true, # set this to false to exclude meta.broken packages from the output
path ? ./../..,
# used by ./attrpaths.nix
# used by ./pre-eval.nix
attrNamesOnly ? false,
# Set this to `null` to build for builtins.currentSystem only
@ -14,6 +14,8 @@
builtins.readFile (path + "/pkgs/top-level/release-supported-systems.json")
),
attrPathsDisallowedForInternalUse ? [ ],
# Customize the config used to evaluate nixpkgs
extraNixpkgsConfig ? { },
}:
@ -35,6 +37,22 @@ let
allowVariants = !attrNamesOnly;
checkMeta = true;
# We don't need to care about problems being caught using the
# standard mechanism, because any problems whose kind is not
# nixpkgsInternalUseAllowed cause the corresponding attributes to
# be disallowed entirely for internal use with
# attrPathsDisallowedForInternalUse, see also ./pre-eval.nix
problems.matchers = lib.mkForce [
# We only need to set the broken handler to error, so that CI
# doesn't evaluate those. No reason it couldn't evaluate them
# afaik, but this is how it's been before.
{
kind = "broken";
handler = "error";
}
];
inherit attrPathsDisallowedForInternalUse;
# Silence the `x86_64-darwin` deprecation warning.
allowDeprecatedx86_64Darwin = true;

128
ci/eval/pre-eval.nix Normal file
View file

@ -0,0 +1,128 @@
# This file does a fast pre-evaluation of Nixpkgs to determine:
# - paths: A *superset* of all attrpaths of derivations which might be part of a release on *any* platform.
# - attrPathsDisallowedForInternalUse: Attribute paths whose meta.problems has problems whose kinds should not be used internally in Nixpkgs
#
# This expression runs single-threaded under all current Nix
# implementations, but much faster and with much less memory
# used than ./outpaths.nix itself.
#
# Once you have the list of attrnames you can split it up into
# $NUM_CORES batches and evaluate the outpaths separately for each
# batch, in parallel.
#
# To dump the result:
#
# nix-instantiate --eval --strict --json ci/eval/pre-eval.nix -A result
#
{
lib ? import (path + "/lib"),
trace ? false,
path ? ./../..,
extraNixpkgsConfigJson ? "{}",
}:
let
# TODO: Use mapAttrsToListRecursiveCond when this PR lands:
# https://github.com/NixOS/nixpkgs/pull/395160
listAttrs =
path: value:
let
result =
if path == [ "AAAAAASomeThingsFailToEvaluate" ] || !(lib.isAttrs value) then
[ ]
else if lib.isDerivation value then
[
{
inherit path value;
}
]
else
lib.pipe value [
(lib.mapAttrsToList (
name: value:
lib.addErrorContext "while evaluating package set attribute path '${
lib.showAttrPath (path ++ [ name ])
}'" (listAttrs (path ++ [ name ]) value)
))
lib.concatLists
];
in
lib.traceIf trace "** ${lib.showAttrPath path}" result;
outpaths = import ./outpaths.nix {
inherit path;
extraNixpkgsConfig = builtins.fromJSON extraNixpkgsConfigJson;
attrNamesOnly = true;
};
list =
map
(path: {
inherit path;
# This looks a bit weird, but the only reason we care about this value
# is for the meta.problems check below, and stdenv's certainly don't
# have any problems, so this is fine :)
value = { };
})
[
# Some of the following are based on variants, which are disabled with `attrNamesOnly = true`.
# Until these have been removed from release.nix / hydra, we manually add them to the list.
[
"pkgsLLVM"
"stdenv"
]
[
"pkgsArocc"
"stdenv"
]
[
"pkgsZig"
"stdenv"
]
[
"pkgsStatic"
"stdenv"
]
[
"pkgsMusl"
"stdenv"
]
]
++ listAttrs [ ] outpaths;
paths = map (attrs: attrs.path) list;
names = map lib.showAttrPath paths;
inherit (import ../../pkgs/stdenv/generic/problems.nix { inherit lib; })
disallowNixpkgsInternalUseKinds
;
# Determine the list of attributes whose packages have any meta.problems
# with a kind that's disallowed from internal Nixpkgs use
attrPathsDisallowedForInternalUse = lib.pipe list [
(lib.map (
attrs:
attrs
// {
problematicProblems = builtins.tryEval (
lib.filterAttrs (name: problem: disallowNixpkgsInternalUseKinds ? ${problem.kind}) (
attrs.value.meta.problems or { }
)
);
}
))
(lib.filter (attrs: attrs.problematicProblems.success && attrs.problematicProblems.value != { }))
(lib.map (attrs: {
attrPath = attrs.path;
reason = "it has certain meta.problems whose kinds are disallowed: ${
lib.generators.toPretty { } attrs.problematicProblems.value
}";
}))
];
in
{
# TODO: Do we still need these? Probably not
inherit paths names;
result = {
inherit paths attrPathsDisallowedForInternalUse;
};
}

View file

@ -50,6 +50,7 @@ let
inherit (import ./problems.nix { inherit lib; })
problemsType
genCheckProblems
completeMetaProblems
;
checkProblems = genCheckProblems config;
@ -639,6 +640,8 @@ let
unsupported = hasUnsupportedPlatform attrs;
insecure = isMarkedInsecure attrs;
problems = completeMetaProblems config attrs;
available =
validity.valid != "no"
&& ((config.checkMetaRecursively or false) -> all (d: d.meta.available or true) references);

View file

@ -72,68 +72,81 @@ rec {
max = a: b: if lessThan a b then b else a;
};
# TODO: Combine this and automaticProblems into a `{ removal = { manual = true; ... }; ... }` structure for less error-prone changes
kinds = rec {
# Automatic and manual problem kinds
known = map (problem: problem.kindName) automaticProblems ++ manual;
# Problem kinds that are currently allowed to be specified in `meta.problems`
manual = [
"removal"
"deprecated"
"broken"
];
# Problem kinds that are currently only allowed to be specified once
unique = [
"removal"
];
kinds = {
maintainerless = {
manualAllowed = false;
isUnique = false;
nixpkgsInternalUseAllowed = true;
automatic = {
condition =
# 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.
config: attrs:
# Order of checks optimised for short-circuiting the common case of having maintainers
(attrs.meta.maintainers or [ ] == [ ])
&& (attrs.meta.teams or [ ] == [ ])
&& (!attrs ? outputHash)
&& (attrs ? meta.description);
value.message = "This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute.";
};
};
broken = {
manualAllowed = true;
isUnique = false;
nixpkgsInternalUseAllowed = true;
automatic = {
condition =
config:
let
# TODO: Consider deprecating this or making it generic for all problems
allowBroken = config.allowBroken || builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1";
# Same thing but a set with null values (comes in handy at times)
manual' = genAttrs manual (k: null);
unique' = genAttrs unique (k: null);
allowBrokenPredicate =
lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2605)
"config.allowBrokenPredicate is deprecated, use config.problems.handlers.myPackage.broken = \"warn\" for individual packages instead."
config.allowBrokenPredicate;
in
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.";
};
};
removal = {
manualAllowed = true;
isUnique = true;
nixpkgsInternalUseAllowed = false;
automatic = null;
};
deprecated = {
manualAllowed = true;
isUnique = false;
nixpkgsInternalUseAllowed = false;
automatic = null;
};
};
automaticProblems = [
{
kindName = "maintainerless";
condition =
# 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.
config: attrs:
# Order of checks optimised for short-circuiting the common case of having maintainers
(attrs.meta.maintainers or [ ] == [ ])
&& (attrs.meta.teams or [ ] == [ ])
&& (!attrs ? outputHash)
&& (attrs ? meta.description);
value.message = "This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute.";
}
{
kindName = "broken";
condition =
config:
let
# TODO: Consider deprecating this or making it generic for all problems
allowBroken = config.allowBroken || builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1";
# Problem kinds that are currently allowed to be specified in `meta.problems`
manualKinds = lib.filterAttrs (name: value: value.manualAllowed) kinds;
# Problem kinds that are currently only allowed to be specified once
uniqueKinds = lib.filterAttrs (name: value: value.isUnique) kinds;
allowBrokenPredicate =
lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2605)
"config.allowBrokenPredicate is deprecated, use config.problems.handlers.myPackage.broken = \"warn\" for individual packages instead."
config.allowBrokenPredicate;
in
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.";
}
];
disallowNixpkgsInternalUseKinds = lib.filterAttrs (
name: value: !value.nixpkgsInternalUseAllowed
) kinds;
automaticProblems = lib.mapAttrsToList (name: value: value.automatic // { kindName = name; }) (
lib.filterAttrs (name: value: value.automatic != null) kinds
);
genAutomaticProblems =
config: attrs:
@ -148,7 +161,7 @@ rec {
let
types = lib.types;
handlerType = types.enum handlers.levels;
problemKindType = types.enum kinds.known;
problemKindType = types.enum (attrNames kinds);
in
{
handlers = lib.mkOption {
@ -250,7 +263,8 @@ rec {
record
enum
;
kindType = enum kinds.manual;
# While we should only allow manual kinds, we need to allow `meta.problems = otherPackage.meta.problems`, which includes automatic ones as well
kindType = enum (attrNames kinds);
subRecord = record {
kind = kindType;
message = str;
@ -270,7 +284,7 @@ rec {
let
kindGroups = groupBy (kind: kind) (mapAttrsToList (name: problem: problem.kind or name) v);
in
all (kind: kinds.manual' ? ${kind} && (kinds.unique' ? ${kind} -> length kindGroups.${kind} == 1)) (
all (kind: kinds ? ${kind} && (uniqueKinds ? ${kind} -> length kindGroups.${kind} == 1)) (
attrNames kindGroups
)
);
@ -294,14 +308,14 @@ rec {
++ concatLists (
mapAttrsToList (
kind: kindGroup:
optionals (!kinds.manual' ? ${kind}) (
optionals (!kinds ? ${kind}) (
map (
el:
"${ctx}.${el.name}: Problem kind ${kind}, inferred from the problem name, is invalid; expected ${kindType.name}. You can specify an explicit problem kind with `${ctx}.${el.name}.kind`"
) (filter (el: !el.explicit) kindGroup)
)
++
optional (kinds.unique' ? ${kind} && length kindGroup > 1)
optional (uniqueKinds ? ${kind} && length kindGroup > 1)
"${ctx}: Problem kind ${kind} should be unique, but is used for these problems: ${
concatMapStringsSep ", " (el: el.name) kindGroup
}"
@ -510,6 +524,12 @@ rec {
in
processProblems pname problemsToHandle;
completeMetaProblems =
config: attrs:
mapAttrs (name: problem: { kind = name; } // problem) (
(attrs.meta.problems or { }) // genAutomaticProblems config attrs
);
processProblems =
pname: problemsToHandle:
let

View file

@ -1,4 +1,4 @@
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
error: Refusing to evaluate package 'a-0' in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:11 because it has an invalid meta attrset:
- a.meta.problems.invalid: Problem kind invalid, inferred from the problem name, is invalid; expected enum<removal,deprecated,broken>. You can specify an explicit problem kind with `a.meta.problems.invalid.kind`
- a.meta.problems.invalid: Problem kind invalid, inferred from the problem name, is invalid; expected enum<broken,deprecated,removal>. You can specify an explicit problem kind with `a.meta.problems.invalid.kind`

View file

@ -58,6 +58,34 @@ let
internal = true;
};
attrPathsDisallowedForInternalUse = mkOption {
type = types.listOf (
types.submodule {
options.attrPath = lib.mkOption {
type = types.listOf types.str;
description = ''
Attribute path to disallow.
'';
};
options.reason = lib.mkOption {
type = types.nullOr types.str;
default = null;
example = /* because */ "it's dangerous.";
description = ''
Reason for it being disallowed.
'';
};
}
);
internal = true;
default = [ ];
description = ''
List of attribute paths that may not be used by other packages in Nixpkgs.
Should usually only be defined by Nixpkgs CI.
'';
};
# Config options
warnUndeclaredOptions = mkOption {

View file

@ -214,7 +214,29 @@ let
;
};
pkgs = boot stages;
fixedPoint = boot stages;
pkgs =
# Generally only set by CI, don't want to cause a performance hit for users
if config.attrPathsDisallowedForInternalUse == [ ] then
fixedPoint
else
# See ./stage.nix, which replaced config.attrPathsDisallowedForInternalUse with aborts.
# We replace these attribute paths with their original derivations again,
# because CI would just error out from the aborting attributes themselves.
# Internally all packages still see the aborting attributes if used as dependencies,
# because we do this here after the fixed-point is calculated.
# Note that we don't want to remove the attributes entirely like what aliases.nix does,
# because unlike aliases, CI still needs to check the packages to evaluate at all,
# which it wouldn't if they're removed entirely.
lib.updateManyAttrsByPath
(map (attrs: {
path = attrs.attrPath;
update =
_:
lib.getAttrFromPath attrs.attrPath fixedPoint.__internalBeforeInternallyDisallowedAttrPathsOverlay;
}) config.attrPathsDisallowedForInternalUse)
(removeAttrs fixedPoint [ "__internalBeforeInternallyDisallowedAttrPathsOverlay" ]);
in
checked pkgs

View file

@ -56,7 +56,7 @@ pkgs.runCommand "nixpkgs-release-checks"
set -x
nix-env -f $src \
--show-trace --argstr system "$platform" \
--arg config '{ allowAliases = false; allowDeprecatedx86_64Darwin = true; }' \
--arg config '{ lib, pkgs }: { problems.matchers = lib.mkForce [ ]; allowAliases = false; allowDeprecatedx86_64Darwin = true; }' \
--option lint-url-literals fatal \
-qa --drv-path --system-filter \* --system \
"''${opts[@]}" 2> eval-warnings.log > packages1
@ -72,7 +72,7 @@ pkgs.runCommand "nixpkgs-release-checks"
nix-env -f $src2 \
--show-trace --argstr system "$platform" \
--arg config '{ allowAliases = false; allowDeprecatedx86_64Darwin = true; }' \
--arg config '{ lib, pkgs }: { problems.matchers = lib.mkForce [ ]; allowAliases = false; allowDeprecatedx86_64Darwin = true; }' \
--option lint-url-literals fatal \
-qa --drv-path --system-filter \* --system \
"''${opts[@]}" > packages2
@ -95,7 +95,7 @@ pkgs.runCommand "nixpkgs-release-checks"
nix-env -f $src \
--show-trace --argstr system "$platform" \
--arg config '{ allowAliases = false; allowDeprecatedx86_64Darwin = true; }' \
--arg config '{ lib, pkgs }: { problems.matchers = lib.mkForce [ ]; allowAliases = false; allowDeprecatedx86_64Darwin = true; }' \
--option lint-url-literals fatal \
-qa --drv-path --system-filter \* --system --meta --xml \
"''${opts[@]}" > /dev/null

View file

@ -316,6 +316,32 @@ let
};
};
# Replaces the attributes in config.attrPathsDisallowedForInternalUse with aborts.
# Not throws because those would be ignored by nix-env, which is what CI uses to evaluate everything
# See also ./default.nix, where these attributes are added back again so they're still checked by CI
internallyDisallowedAttrPathsOverlay =
final: prev:
# Generally only set by CI, don't want to cause a performance hit for users
if config.attrPathsDisallowedForInternalUse == [ ] then
{ }
else
{
# So that ./default.nix can add them back again outside the fixed point
# Don't use this in packages!
__internalBeforeInternallyDisallowedAttrPathsOverlay = prev;
}
// lib.updateManyAttrsByPath (map (
{ attrPath, reason }:
{
path = attrPath;
update =
_:
abort "${lib.concatStringsSep "." attrPath} is disallowed from being used within Nixpkgs${
lib.optionalString (reason != null) ", because ${reason}"
}";
}
) config.attrPathsDisallowedForInternalUse) prev;
# The complete chain of package set builders, applied from top to bottom.
# stdenvOverlays must be last as it brings package forward from the
# previous bootstrapping phases which have already been overlaid.
@ -331,6 +357,7 @@ let
aliases
variants
configOverrides
internallyDisallowedAttrPathsOverlay
]
++ overlays
++ [