diff --git a/doc/redirects.json b/doc/redirects.json index ffa10799fa6fb..3d4e601d942d2 100644 --- a/doc/redirects.json +++ b/doc/redirects.json @@ -611,6 +611,9 @@ "chap-stdenv": [ "index.html#chap-stdenv" ], + "sec-problems": [ + "index.html#sec-problems" + ], "sec-using-llvm": [ "index.html#sec-using-llvm" ], diff --git a/doc/using/configuration.chapter.md b/doc/using/configuration.chapter.md index ac67d7a614595..70443dec698e1 100644 --- a/doc/using/configuration.chapter.md +++ b/doc/using/configuration.chapter.md @@ -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} @@ -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. diff --git a/pkgs/stdenv/generic/check-meta.nix b/pkgs/stdenv/generic/check-meta.nix index 28de9c68110e8..1777c1b1da23c 100644 --- a/pkgs/stdenv/generic/check-meta.nix +++ b/pkgs/stdenv/generic/check-meta.nix @@ -10,10 +10,8 @@ let inherit (lib) all - attrNames attrValues concatMapStrings - concatMapStringsSep concatStrings filter findFirst @@ -26,7 +24,8 @@ let optionalString isAttrs isString - mapAttrs + warn + foldl' ; inherit (lib.lists) @@ -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 { config = { showDerivationWarnings = [ "maintainerless" ]; }; } - showWarnings = config.showDerivationWarnings; getNameWithVersion = attrs: attrs.name or "${attrs.pname or "«name-missing»"}-${attrs.version or "«version-missing»"}"; @@ -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 @@ -305,7 +291,7 @@ let ${concatStrings (map (output: " - ${output}\n") missingOutputs)} ''; - metaTypes = + metaType = let types = import ./meta-types.nix { inherit lib; }; inherit (types) @@ -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; @@ -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 = @@ -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 @@ -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 = ""; } @@ -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: @@ -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 diff --git a/pkgs/stdenv/generic/meta-types.nix b/pkgs/stdenv/generic/meta-types.nix index a51ae2b127113..35243c546f297 100644 --- a/pkgs/stdenv/generic/meta-types.nix +++ b/pkgs/stdenv/generic/meta-types.nix @@ -12,14 +12,21 @@ let isList all any + attrNames attrValues + concatMap isFunction isBool concatStringsSep + concatMapStringsSep isFloat + elem + mapAttrs ; isTypeDef = t: isAttrs t && t ? name && isString t.name && t ? verify && isFunction t.verify; + pretty = lib.generators.toPretty { indent = " "; }; + # TODO: Add errors to all types in lib.fix (self: { string = { @@ -62,13 +69,21 @@ lib.fix (self: { t: assert isTypeDef t; let - inherit (t) verify; + inherit (t) verify errors; in { name = "attrsOf<${t.name}>"; verify = # attrsOf can be optimised to just isAttrs if t == self.any then isAttrs else attrs: isAttrs attrs && all verify (attrValues attrs); + errors = + ctx: attrs: + if !isAttrs attrs then + [ "${ctx}: Invalid value; expected an attribute set, got\n ${pretty attrs}" ] + else + concatMap (name: lib.optionals (!verify attrs.${name}) (errors "${ctx}.${name}" attrs.${name})) ( + attrNames attrs + ); }; listOf = @@ -95,4 +110,48 @@ lib.fix (self: { name = "union<${concatStringsSep "," (map (t: t.name) types)}>"; verify = v: any (func: func v) funcs; }; + + enum = + values: + assert isList values && all isString values; + { + name = "enum<${concatStringsSep "," values}>"; + verify = v: isString v && elem v values; + }; + + record = + fields: + assert isAttrs fields && all isTypeDef (attrValues fields); + let + # Map attrs directly to the verify function for performance + fieldVerifiers = mapAttrs (_: t: t.verify) fields; + in + { + name = "record"; + verify = v: isAttrs v && all (k: fieldVerifiers ? ${k} && fieldVerifiers.${k} v.${k}) (attrNames v); + errors = + ctx: v: + if !isAttrs v then + [ "${ctx}: Invalid value; expected attribute set, got\n ${pretty v}" ] + else + concatMap ( + k: + if fieldVerifiers ? ${k} then + if fieldVerifiers.${k} v.${k} then + [ ] + else + (fields.${k}.errors or (ctx': v': [ + "${ctx'}: Invalid value; expected ${fields.${k}.name}, got\n ${pretty v'}" + ]) + ) + (ctx + ".${k}") + v.${k} + else + [ + "${ctx}: key '${k}' is unrecognized; expected one of: \n [${ + concatMapStringsSep ", " (x: "'${x}'") (attrNames fields) + }]" + ] + ) (attrNames v); + }; }) diff --git a/pkgs/stdenv/generic/problems.nix b/pkgs/stdenv/generic/problems.nix new file mode 100644 index 0000000000000..13930a60e6b7d --- /dev/null +++ b/pkgs/stdenv/generic/problems.nix @@ -0,0 +1,554 @@ +/* + This file implements everything around meta.problems, including: + - automaticProblems: Which problems get added automatically based on some condition + - configOptions: Module system options for config.problems + - problemsType: The check for meta.problems + - genHandlerSwitch: The logic to determine the handler for a specific problem based on config.problems + - genCheckProblems: The logic to determine which problems need to be handled and how the messages should look like + + There are tests to cover pretty much this entire file, so please run them when making changes ;) + + nix-build -A tests.problems +*/ + +{ lib }: + +rec { + + inherit (lib.strings) + escapeNixIdentifier + ; + + inherit (lib) + any + listToAttrs + concatStringsSep + optionalString + getName + optionalAttrs + pipe + isString + filterAttrs + mapAttrs + partition + elemAt + max + foldl' + elem + filter + concatMapStringsSep + optional + optionals + concatLists + all + attrNames + attrValues + length + mapAttrsToList + groupBy + subtractLists + genAttrs + ; + + handlers = rec { + # Ordered from less to more + levels = [ + "ignore" + "warn" + "error" + ]; + + lessThan = + a: b: + if a == "error" then + false + else if a == "warn" then + b == "error" + else + b != "ignore"; + + max = a: b: if lessThan a b then b else a; + }; + + kinds = rec { + known = [ + "removal" + "deprecated" + "maintainerless" + "insecure" + "broken" + "unsupported" + ]; + # Problem kinds that are currently allowed in `meta.problems` + manual = [ + "removal" + "deprecated" + ]; + # Problem kinds that are only allowed up to once per package + unique = [ + "removal" + "maintainerless" + ]; + # known \ manual + reserved = subtractLists manual known; + + # Same thing but a set with null values (comes in handy at times) + manual' = genAttrs manual (k: null); + unique' = genAttrs unique (k: 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. + 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."; + } + ]; + + genAutomaticProblems = + attrs: + listToAttrs ( + map (problem: lib.nameValuePair problem.kindName problem.value) ( + filter (problem: problem.condition attrs) automaticProblems + ) + ); + + # A module system type for Nixpkgs config + configOptions = + let + types = lib.types; + handlerType = types.enum handlers.levels; + problemKindType = types.enum kinds.known; + in + { + handlers = lib.mkOption { + type = with types; attrsOf (attrsOf handlerType); + default = { }; + description = '' + Specify how to handle packages with problems. + Each key has the format `packageName.problemName`, each value is one of "error", "warn" or "ignore". + + This option takes precedence over anything in `problems.matchers`. + + Package names are taken from `lib.getName`, which looks at the `pname` first and falls back to extracting the "pname" part from the `name` attribute. + + See Installing packages with problems in the NixOS manual. + ''; + }; + + matchers = lib.mkOption { + type = types.listOf ( + types.submodule ( + { config, ... }: + { + options = { + package = lib.mkOption { + type = types.nullOr types.str; + description = "Match problems of packages with this name"; + default = null; + }; + name = lib.mkOption { + type = types.nullOr types.str; + description = "Match problems with this problem name"; + default = null; + }; + kind = lib.mkOption { + type = types.nullOr problemKindType; + description = "Match problems of this problem kind"; + default = null; + }; + handler = lib.mkOption { + type = handlerType; + description = "Specify the handler for matched problems"; + }; + + # Temporary hack to get assertions in submodules, see global assertions below + assertions = lib.mkOption { + type = types.listOf types.anything; + default = [ ]; + internal = true; + }; + }; + config = { + assertions = [ + # TODO: Does it really matter if we let people specify this? Maybe not, so consider removing this assertion + { + assertion = !(config.package != null && config.name != null); + message = ''A matcher cannot match on both a package and problem name as this would not be a wildcard, for that use `problems.handlers = { ${toString config.package}.${toString config.name} = "${toString config.handler}"; };` instead''; + } + ]; + }; + } + ) + ); + default = [ ]; + description = '' + A more powerful and less ergonomic version of `problems.handlers`. + Each value is a matcher, that may match onto certain properties of a problem and specify a handler for them. + + If multiple matchers match a problem, the handler with the highest severity (error > warn > ignore) will be used. + Values in `problems.handlers` always take precedence over matchers. + + Any matchers must not contain both a `package` and `name` field, for this should be handled by using `problems.handlers` instead. + ''; + example = [ + { + kind = "maintainerless"; + handler = "warn"; + } + { + package = "myPackageICareAbout"; + handler = "error"; + } + ]; + }; + }; + + # The type for meta.problems + problemsType = + let + types = import ./meta-types.nix { inherit lib; }; + inherit (types) + str + listOf + attrsOf + record + enum + ; + kindType = enum kinds.manual; + subRecord = record { + kind = kindType; + message = str; + urls = listOf str; + }; + simpleType = attrsOf subRecord; + in + { + name = "problems"; + verify = + v: + v == { } + || + simpleType.verify v + && all (problem: problem ? message) (attrValues v) + && ( + 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)) ( + attrNames kindGroups + ) + ); + errors = + ctx: v: + let + kindGroups = groupBy (attrs: attrs.kind) ( + mapAttrsToList (name: problem: { + inherit name; + explicit = problem ? kind; + kind = problem.kind or name; + }) v + ); + in + if !simpleType.verify v then + simpleType.errors ctx v + else + concatLists ( + mapAttrsToList (name: p: optional (!p ? message) "${ctx}.${name}: `.message` not specified") v + ) + ++ concatLists ( + mapAttrsToList ( + k: ks: + optionals (!kinds.manual' ? ${k}) ( + map ( + k': + "${ctx}.${k'.name}: Problem kind ${k}, inferred from the problem name, is invalid; expected ${kindType.name}. You can specify an explicit problem kind with `${ctx}.${k'.name}.kind`" + ) (filter (k': !k'.explicit) ks) + ) + ++ + optional (kinds.unique' ? ${k} && length ks > 1) + "${ctx}: Problem kind ${k} should be unique, but is used for these problems: ${ + concatMapStringsSep ", " (k': k'.name) ks + }" + ) kindGroups + ); + }; + + /* + Construct a structure as follows, with the invariant that a more specific path always has a stricter handler. E.g. if byPackage.foo.fallback.fallback == "warn", then fallback.fallback.fallback must be "ignore". + + The unspecific/specific handlers for a specific problem form a lattice. + + specific. = { + specific. = { + specific. = ; + fallback = ; + }; + fallback = { + specific. = ; + fallback = ; + }; + }; + fallback = { + specific. = { + specific. = ; + fallback = ; + }; + fallback = { + specific. = ; + fallback = ; + }; + }; + + We can query this structure with: + + switch: + let + forPackage = switch.specific.${package} or switch.fallback; + forName = forPackage.specific.${name} or forPackage.fallback; + handler = forName.specific.${kind} or forName.fallback; + in + handler + + which should be fairly quick + } + */ + genHandlerSwitch = + config: + let + identOrder = [ + "kind" + "name" + "package" + ]; + constraints' = + # matchers have low priority + map (m: m // { priority = 0; }) config.problems.matchers + # handlers have higher priority + ++ concatLists ( + mapAttrsToList ( + package: forPackage: + mapAttrsToList (name: handler: { + inherit package name handler; + kind = null; + priority = 1; + }) forPackage + ) config.problems.handlers + ); + + constraints = filter ( + matcher: + (elem matcher.name kinds.reserved -> (isNull matcher.kind || matcher.kind == matcher.name)) + && (elem matcher.kind kinds.reserved -> (isNull matcher.name || matcher.kind == matcher.name)) + ) constraints'; + + getHandler = + list: + (foldl' + (acc: el: { + priority = max acc.priority el.priority; + handler = + if acc.priority == el.priority then + handlers.max acc.handler el.handler + else if acc.priority > el.priority then + acc.handler + else + el.handler; + }) + { + priority = 0; + # TODO: Set the default depending on the kind + handler = "ignore"; + } + list + ).handler; + + doLevel = + index: + let + ident = elemAt identOrder index; + nextLevel = if index + 1 == length identOrder then getHandler else doLevel (index + 1); + in + list: + let + # Partition all matchers into ident-specific (.wrong) and -unspecific (.right) ones + parted = partition (m: isNull m.${ident}) list; + # We only use the unspecific ones to compute the fallback + fallback = nextLevel parted.right; + # TODO: If the name or kind is reserved, then we can be sure that it has the same name as kind, so any paths where those don't match can be discarded + specific = pipe parted.wrong [ + (groupBy (m: m.${ident})) + # For ident-specific handlers, the unspecific ones also apply + (mapAttrs (package: handlers: nextLevel (handlers ++ parted.right))) + # Optimisation: Don't need a specific handler if it would end up the same as the fallback + (filterAttrs (name: res: res != fallback)) + ]; + in + # Optimisation in case it's always the same handler, + # can propagate up for the entire switch to just be a string + if specific == { } && isString fallback then + fallback + else + { + "${ident}Fallback" = fallback; + "${ident}Specific" = specific; + }; + switch = doLevel 0 constraints; + in + { + inherit switch; + handlerForProblem = + if isString switch then + pname: name: kind: + switch + else + pname: name: kind: + let + switch' = switch.kindSpecific.${kind} or switch.kindFallback; + in + if isString switch' then + switch' + else + let + switch'' = switch'.nameSpecific.${name} or switch'.nameFallback; + in + if isString switch'' then + switch'' + else + switch''.packageSpecific.${pname} or switch''.packageFallback; + }; + + genCheckProblems = + config: + let + # This is here so that it gets cached for a (checkProblems config) thunk + inherit (genHandlerSwitch config) + handlerForProblem + ; + 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" + ) automaticProblems + && ( + # No manual problems + manualProblems == { } + # Or all manual problems are ignored + || all (name: handlerForProblem pname name (manualProblems.${name}.kind or name) == "ignore") ( + attrNames manualProblems + ) + ) + then + null + else + # Slow path, only here we actually figure out which problems we need to handle + let + problems = attrs.meta.problems or { } // genAutomaticProblems 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; + inherit problem; + }) problems + ); + in + processProblems pname problemsToHandle; + + processProblems = + pname: problemsToHandle: + let + grouped = groupBy (v: v.handler) problemsToHandle; + + warnProblems = grouped.warn or [ ]; + errorProblems = grouped.error or [ ]; + + # assert annotatedProblems != [ ]; + fullMessage = + v: + "${v.name}${optionalString (v.kind != v.name) " (kind \"${v.kind}\""}: " + + "${v.problem.message}${ + optionalString (v.problem.urls or [ ] != [ ]) " (${concatStringsSep ", " v.problem.urls})" + }"; + + warnings = map (x: { + reason = "problem"; + errormsg = "has the following problem: ${fullMessage x}"; + remediation = ""; # TODO, maybe just link to docs to keep it small + }) warnProblems; + + overrideHandlerString = + indentation: + concatMapStringsSep ("\n" + indentation) ( + problem: ''${escapeNixIdentifier pname}.${escapeNixIdentifier problem.name} = "warn";'' + ) errorProblems; + + error = + if errorProblems == [ ] then + null + else + { + reason = "problem"; + errormsg = "has the following problems that must be acknowledged: [ ${ + concatMapStringsSep " " (attrs: "\"${attrs.name}\"") errorProblems + } ]"; + ## TODO: Add mention of problem.matchers, or maybe better link to docs of that + remediation = '' + + Package problems: + + ${concatMapStringsSep "\n" (x: "- ${fullMessage x}") errorProblems} + + You can use it anyway by ignoring its problems, using one of the + following methods: + + a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + ${overrideHandlerString " "} + }; + } + + b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + ${overrideHandlerString " "} + }; + } + ''; + }; + in + { + inherit error warnings; + }; + +} diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index 4c613135e9771..c0481d9a14e74 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -187,6 +187,10 @@ in texlive = recurseIntoAttrs (callPackage ./texlive { }); + problems = callPackage ./problems { }; + # Temporarily disabled so we can see the performance comparison + #problems = recurseIntoAttrs (callPackage ./problems { }); + cuda = callPackage ./cuda { }; trivial-builders = callPackage ../build-support/trivial-builders/test/default.nix { }; diff --git a/pkgs/test/problems/cases/deprecated-and-removal-and-maintainerless-error/default.nix b/pkgs/test/problems/cases/deprecated-and-removal-and-maintainerless-error/default.nix new file mode 100644 index 0000000000000..496c562890a56 --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-and-removal-and-maintainerless-error/default.nix @@ -0,0 +1,24 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."deprecated" = "error"; + "a"."removal" = "error"; + "a"."maintainerless" = "error"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.problems = { + deprecated.message = "Package is deprecated and replaced by b."; + removal.message = "Package will be removed."; + }; + meta.maintainers = [ ]; +} diff --git a/pkgs/test/problems/cases/deprecated-and-removal-and-maintainerless-error/expected-stderr b/pkgs/test/problems/cases/deprecated-and-removal-and-maintainerless-error/expected-stderr new file mode 100644 index 0000000000000..01b4e3d4098b9 --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-and-removal-and-maintainerless-error/expected-stderr @@ -0,0 +1,37 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:18 has the following problems that must be acknowledged: [ "deprecated" "maintainerless" "removal" ], refusing to evaluate. + + +Package problems: + +- deprecated: Package is deprecated and replaced by b. +- maintainerless: This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute. +- removal: Package will be removed. + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.deprecated = "warn"; + a.maintainerless = "warn"; + a.removal = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.deprecated = "warn"; + a.maintainerless = "warn"; + a.removal = "warn"; + }; + } diff --git a/pkgs/test/problems/cases/deprecated-and-removal-error/default.nix b/pkgs/test/problems/cases/deprecated-and-removal-error/default.nix new file mode 100644 index 0000000000000..20a18cc17c78e --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-and-removal-error/default.nix @@ -0,0 +1,22 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."deprecated" = "error"; + "a"."removal" = "error"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems = { + deprecated.message = "Package is deprecated and replaced by b"; + removal.message = "Package will be removed"; + }; + meta.maintainers = [ "hello" ]; +} diff --git a/pkgs/test/problems/cases/deprecated-and-removal-error/expected-stderr b/pkgs/test/problems/cases/deprecated-and-removal-error/expected-stderr new file mode 100644 index 0000000000000..f144fddeb64df --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-and-removal-error/expected-stderr @@ -0,0 +1,34 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:16 has the following problems that must be acknowledged: [ "deprecated" "removal" ], refusing to evaluate. + + +Package problems: + +- deprecated: Package is deprecated and replaced by b +- removal: Package will be removed + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.deprecated = "warn"; + a.removal = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.deprecated = "warn"; + a.removal = "warn"; + }; + } diff --git a/pkgs/test/problems/cases/deprecated-error/default.nix b/pkgs/test/problems/cases/deprecated-error/default.nix new file mode 100644 index 0000000000000..405d394bd893f --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-error/default.nix @@ -0,0 +1,20 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."deprecated" = "error"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems.deprecated = { + message = "Package is deprecated and replaced by b"; + }; + meta.maintainers = [ "hello" ]; +} diff --git a/pkgs/test/problems/cases/deprecated-error/expected-stderr b/pkgs/test/problems/cases/deprecated-error/expected-stderr new file mode 100644 index 0000000000000..d55dcd7dfce5c --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-error/expected-stderr @@ -0,0 +1,31 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:15 has the following problems that must be acknowledged: [ "deprecated" ], refusing to evaluate. + + +Package problems: + +- deprecated: Package is deprecated and replaced by b + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.deprecated = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.deprecated = "warn"; + }; + } diff --git a/pkgs/test/problems/cases/deprecated-ignore/default.nix b/pkgs/test/problems/cases/deprecated-ignore/default.nix new file mode 100644 index 0000000000000..0ce36d2948423 --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-ignore/default.nix @@ -0,0 +1,18 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."deprecated" = "ignore"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems.deprecated.message = "Package is deprecated and is replaced by b."; + meta.maintainers = [ "hello" ]; +} diff --git a/pkgs/test/problems/cases/deprecated-ignore/expected-stderr b/pkgs/test/problems/cases/deprecated-ignore/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-ignore/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/deprecated-warn/default.nix b/pkgs/test/problems/cases/deprecated-warn/default.nix new file mode 100644 index 0000000000000..bcafc3e96f6a7 --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-warn/default.nix @@ -0,0 +1,18 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."deprecated" = "warn"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems.deprecated.message = "Package a is deprecated and superseeded by b."; + meta.maintainers = [ "hello" ]; +} diff --git a/pkgs/test/problems/cases/deprecated-warn/expected-stderr b/pkgs/test/problems/cases/deprecated-warn/expected-stderr new file mode 100644 index 0000000000000..62565751c5ea7 --- /dev/null +++ b/pkgs/test/problems/cases/deprecated-warn/expected-stderr @@ -0,0 +1,2 @@ +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:15 has the following problem: deprecated: Package a is deprecated and superseeded by b. Continuing anyway. +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/invalid-kind-error/default.nix b/pkgs/test/problems/cases/invalid-kind-error/default.nix new file mode 100644 index 0000000000000..acb4d9e8b43ec --- /dev/null +++ b/pkgs/test/problems/cases/invalid-kind-error/default.nix @@ -0,0 +1,15 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config.checkMeta = true; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems = { + invalid.message = "No maintainers"; + }; +} diff --git a/pkgs/test/problems/cases/invalid-kind-error/expected-stderr b/pkgs/test/problems/cases/invalid-kind-error/expected-stderr new file mode 100644 index 0000000000000..445d4b495fa22 --- /dev/null +++ b/pkgs/test/problems/cases/invalid-kind-error/expected-stderr @@ -0,0 +1,5 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:11 has an invalid meta attrset: + - a.meta.problems.invalid: Problem kind invalid, inferred from the problem name, is invalid; expected enum. You can specify an explicit problem kind with `a.meta.problems.invalid.kind` +, refusing to evaluate. diff --git a/pkgs/test/problems/cases/maintainerless-and-removal-and-deprecated-warn/default.nix b/pkgs/test/problems/cases/maintainerless-and-removal-and-deprecated-warn/default.nix new file mode 100644 index 0000000000000..6c58ecf5599cc --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-and-removal-and-deprecated-warn/default.nix @@ -0,0 +1,24 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."maintainerless" = "warn"; + "a"."deprecated" = "warn"; + "a"."removal" = "warn"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.maintainers = [ ]; + meta.problems = { + removal.message = "Package to be removed."; + deprecated.message = "Package will be deprecated."; + }; +} diff --git a/pkgs/test/problems/cases/maintainerless-and-removal-and-deprecated-warn/expected-stderr b/pkgs/test/problems/cases/maintainerless-and-removal-and-deprecated-warn/expected-stderr new file mode 100644 index 0000000000000..376dc9f4c5a01 --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-and-removal-and-deprecated-warn/expected-stderr @@ -0,0 +1,4 @@ +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:18 has the following problem: deprecated: Package will be deprecated. Continuing anyway. +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:18 has the following problem: maintainerless: This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute. Continuing anyway. +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:18 has the following problem: removal: Package to be removed. Continuing anyway. +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/maintainerless-and-removal-warn/default.nix b/pkgs/test/problems/cases/maintainerless-and-removal-warn/default.nix new file mode 100644 index 0000000000000..7d672a89f7c58 --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-and-removal-warn/default.nix @@ -0,0 +1,20 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."maintainerless" = "warn"; + "a"."removal" = "warn"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.maintainers = [ ]; + meta.problems.removal.message = "Package will be removed."; +} diff --git a/pkgs/test/problems/cases/maintainerless-and-removal-warn/expected-stderr b/pkgs/test/problems/cases/maintainerless-and-removal-warn/expected-stderr new file mode 100644 index 0000000000000..afeda20f11b1a --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-and-removal-warn/expected-stderr @@ -0,0 +1,3 @@ +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:17 has the following problem: maintainerless: This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute. Continuing anyway. +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:17 has the following problem: removal: Package will be removed. Continuing anyway. +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/maintainerless-error/default.nix b/pkgs/test/problems/cases/maintainerless-error/default.nix new file mode 100644 index 0000000000000..e925b45e08624 --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-error/default.nix @@ -0,0 +1,18 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."maintainerless" = "error"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.maintainers = [ ]; +} diff --git a/pkgs/test/problems/cases/maintainerless-error/expected-stderr b/pkgs/test/problems/cases/maintainerless-error/expected-stderr new file mode 100644 index 0000000000000..7ba0f9f8ae5d9 --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-error/expected-stderr @@ -0,0 +1,31 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:16 has the following problems that must be acknowledged: [ "maintainerless" ], refusing to evaluate. + + +Package problems: + +- maintainerless: This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute. + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.maintainerless = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.maintainerless = "warn"; + }; + } diff --git a/pkgs/test/problems/cases/maintainerless-ignore/default.nix b/pkgs/test/problems/cases/maintainerless-ignore/default.nix new file mode 100644 index 0000000000000..8531510073d40 --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-ignore/default.nix @@ -0,0 +1,17 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."maintainerless" = "ignore"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.maintainers = [ ]; +} diff --git a/pkgs/test/problems/cases/maintainerless-ignore/expected-stderr b/pkgs/test/problems/cases/maintainerless-ignore/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-ignore/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/maintainerless-warn/default.nix b/pkgs/test/problems/cases/maintainerless-warn/default.nix new file mode 100644 index 0000000000000..abaf4c5c2b165 --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-warn/default.nix @@ -0,0 +1,18 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."maintainerless" = "warn"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.maintainers = [ ]; +} diff --git a/pkgs/test/problems/cases/maintainerless-warn/expected-stderr b/pkgs/test/problems/cases/maintainerless-warn/expected-stderr new file mode 100644 index 0000000000000..9e13c29a3430c --- /dev/null +++ b/pkgs/test/problems/cases/maintainerless-warn/expected-stderr @@ -0,0 +1,2 @@ +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:16 has the following problem: maintainerless: This package has no declared maintainer, i.e. an empty `meta.maintainers` and `meta.teams` attribute. Continuing anyway. +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/no-message/default.nix b/pkgs/test/problems/cases/no-message/default.nix new file mode 100644 index 0000000000000..bc89ad823f5da --- /dev/null +++ b/pkgs/test/problems/cases/no-message/default.nix @@ -0,0 +1,22 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + checkMeta = true; + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.problems.removal = { }; +} diff --git a/pkgs/test/problems/cases/no-message/expected-stderr b/pkgs/test/problems/cases/no-message/expected-stderr new file mode 100644 index 0000000000000..1fb1bdf450a6f --- /dev/null +++ b/pkgs/test/problems/cases/no-message/expected-stderr @@ -0,0 +1,5 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:20 has an invalid meta attrset: + - a.meta.problems.removal: `.message` not specified +, refusing to evaluate. diff --git a/pkgs/test/problems/cases/no-problems-description/default.nix b/pkgs/test/problems/cases/no-problems-description/default.nix new file mode 100644 index 0000000000000..66a201eab4031 --- /dev/null +++ b/pkgs/test/problems/cases/no-problems-description/default.nix @@ -0,0 +1,19 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; +} diff --git a/pkgs/test/problems/cases/no-problems-description/expected-stderr b/pkgs/test/problems/cases/no-problems-description/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/no-problems-description/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/no-problems-fod/default.nix b/pkgs/test/problems/cases/no-problems-fod/default.nix new file mode 100644 index 0000000000000..5b7cb9fdb2028 --- /dev/null +++ b/pkgs/test/problems/cases/no-problems-fod/default.nix @@ -0,0 +1,21 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + outputHash = pkgs.lib.fakeHash; +} diff --git a/pkgs/test/problems/cases/no-problems-fod/expected-stderr b/pkgs/test/problems/cases/no-problems-fod/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/no-problems-fod/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/no-problems-team-maintainer/default.nix b/pkgs/test/problems/cases/no-problems-team-maintainer/default.nix new file mode 100644 index 0000000000000..605bf0c2f6962 --- /dev/null +++ b/pkgs/test/problems/cases/no-problems-team-maintainer/default.nix @@ -0,0 +1,21 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.teams = [ "hello" ]; + meta.description = "Some package"; +} diff --git a/pkgs/test/problems/cases/no-problems-team-maintainer/expected-stderr b/pkgs/test/problems/cases/no-problems-team-maintainer/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/no-problems-team-maintainer/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/no-problems/default.nix b/pkgs/test/problems/cases/no-problems/default.nix new file mode 100644 index 0000000000000..2780c964ae4bc --- /dev/null +++ b/pkgs/test/problems/cases/no-problems/default.nix @@ -0,0 +1,21 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.maintainers = [ "hello" ]; + meta.description = "Some package"; +} diff --git a/pkgs/test/problems/cases/no-problems/expected-stderr b/pkgs/test/problems/cases/no-problems/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/no-problems/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/non-attrs-problem/default.nix b/pkgs/test/problems/cases/non-attrs-problem/default.nix new file mode 100644 index 0000000000000..4be3e96d97dc5 --- /dev/null +++ b/pkgs/test/problems/cases/non-attrs-problem/default.nix @@ -0,0 +1,23 @@ +{ + nixpkgs, +}: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + checkMeta = true; + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems.florp = true; +} diff --git a/pkgs/test/problems/cases/non-attrs-problem/expected-stderr b/pkgs/test/problems/cases/non-attrs-problem/expected-stderr new file mode 100644 index 0000000000000..00b079705d960 --- /dev/null +++ b/pkgs/test/problems/cases/non-attrs-problem/expected-stderr @@ -0,0 +1,6 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:19 has an invalid meta attrset: + - a.meta.problems.florp: Invalid value; expected attribute set, got + true +, refusing to evaluate. diff --git a/pkgs/test/problems/cases/problem-urls/default.nix b/pkgs/test/problems/cases/problem-urls/default.nix new file mode 100644 index 0000000000000..1674e046caf9c --- /dev/null +++ b/pkgs/test/problems/cases/problem-urls/default.nix @@ -0,0 +1,28 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.matchers = [ + { + package = "a"; + handler = "error"; + } + ]; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.maintainers = [ "hello" ]; + meta.description = "Some package"; + meta.problems.removal = { + message = "Removed because of XYZ."; + urls = [ + "https://example.com" + "https://anotherexample.com" + ]; + }; +} diff --git a/pkgs/test/problems/cases/problem-urls/expected-stderr b/pkgs/test/problems/cases/problem-urls/expected-stderr new file mode 100644 index 0000000000000..3b659f40d73e5 --- /dev/null +++ b/pkgs/test/problems/cases/problem-urls/expected-stderr @@ -0,0 +1,31 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:20 has the following problems that must be acknowledged: [ "removal" ], refusing to evaluate. + + +Package problems: + +- removal: Removed because of XYZ. (https://example.com, https://anotherexample.com) + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.removal = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.removal = "warn"; + }; + } diff --git a/pkgs/test/problems/cases/removal-error/default.nix b/pkgs/test/problems/cases/removal-error/default.nix new file mode 100644 index 0000000000000..bee7e4fa853f3 --- /dev/null +++ b/pkgs/test/problems/cases/removal-error/default.nix @@ -0,0 +1,15 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems = { + removal.message = "This package has been abandoned upstream and will be removed."; + }; +} diff --git a/pkgs/test/problems/cases/removal-error/expected-stderr b/pkgs/test/problems/cases/removal-error/expected-stderr new file mode 100644 index 0000000000000..f37702aa36ce3 --- /dev/null +++ b/pkgs/test/problems/cases/removal-error/expected-stderr @@ -0,0 +1,31 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:11 has the following problems that must be acknowledged: [ "removal" ], refusing to evaluate. + + +Package problems: + +- removal: This package has been abandoned upstream and will be removed. + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.removal = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.removal = "warn"; + }; + } diff --git a/pkgs/test/problems/cases/removal-ignore/default.nix b/pkgs/test/problems/cases/removal-ignore/default.nix new file mode 100644 index 0000000000000..0cccb8873b276 --- /dev/null +++ b/pkgs/test/problems/cases/removal-ignore/default.nix @@ -0,0 +1,17 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."removal" = "ignore"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems.removal.message = "To be removed"; +} diff --git a/pkgs/test/problems/cases/removal-ignore/expected-stderr b/pkgs/test/problems/cases/removal-ignore/expected-stderr new file mode 100644 index 0000000000000..c255c1d5a32bd --- /dev/null +++ b/pkgs/test/problems/cases/removal-ignore/expected-stderr @@ -0,0 +1 @@ +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/removal-twice-error/default.nix b/pkgs/test/problems/cases/removal-twice-error/default.nix new file mode 100644 index 0000000000000..7898a9ae30eec --- /dev/null +++ b/pkgs/test/problems/cases/removal-twice-error/default.nix @@ -0,0 +1,22 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config.checkMeta = true; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.description = "Some package"; + meta.problems = { + removal = { + message = "This package has been abandoned upstream and will be removed"; + }; + removal2 = { + kind = "removal"; + message = "This package has been abandoned upstream and will be removed2"; + }; + }; +} diff --git a/pkgs/test/problems/cases/removal-twice-error/expected-stderr b/pkgs/test/problems/cases/removal-twice-error/expected-stderr new file mode 100644 index 0000000000000..998934e3c06c1 --- /dev/null +++ b/pkgs/test/problems/cases/removal-twice-error/expected-stderr @@ -0,0 +1,5 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:12 has an invalid meta attrset: + - a.meta.problems: Problem kind removal should be unique, but is used for these problems: removal, removal2 +, refusing to evaluate. diff --git a/pkgs/test/problems/cases/removal-warn/default.nix b/pkgs/test/problems/cases/removal-warn/default.nix new file mode 100644 index 0000000000000..0044c2fc39e09 --- /dev/null +++ b/pkgs/test/problems/cases/removal-warn/default.nix @@ -0,0 +1,18 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { + problems.handlers = { + "a"."removal" = "warn"; + }; + }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.maintainers = [ "hello" ]; + meta.problems.removal.message = "Package to be removed."; +} diff --git a/pkgs/test/problems/cases/removal-warn/expected-stderr b/pkgs/test/problems/cases/removal-warn/expected-stderr new file mode 100644 index 0000000000000..c9515165ab3f3 --- /dev/null +++ b/pkgs/test/problems/cases/removal-warn/expected-stderr @@ -0,0 +1,2 @@ +evaluation warning: Package a-0 in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:15 has the following problem: removal: Package to be removed. Continuing anyway. +warning: you did not specify '--add-root'; the result might be removed by the garbage collector diff --git a/pkgs/test/problems/cases/removed-error/default.nix b/pkgs/test/problems/cases/removed-error/default.nix new file mode 100644 index 0000000000000..43b62a3fcf51b --- /dev/null +++ b/pkgs/test/problems/cases/removed-error/default.nix @@ -0,0 +1,15 @@ +{ nixpkgs }: +let + pkgs = import nixpkgs { + system = "x86_64-linux"; + overlays = [ ]; + config = { }; + }; +in +pkgs.stdenvNoCC.mkDerivation { + pname = "a"; + version = "0"; + meta.problems = { + removal.message = "This package has been abandoned upstream and will be removed"; + }; +} diff --git a/pkgs/test/problems/cases/removed-error/expected-stderr b/pkgs/test/problems/cases/removed-error/expected-stderr new file mode 100644 index 0000000000000..18ad7c78001cd --- /dev/null +++ b/pkgs/test/problems/cases/removed-error/expected-stderr @@ -0,0 +1,31 @@ +(stack trace truncated; use '--show-trace' to show the full, detailed trace) + +error: Package ‘a-0’ in /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-default.nix:11 has the following problems that must be acknowledged: [ "removal" ], refusing to evaluate. + + +Package problems: + +- removal: This package has been abandoned upstream and will be removed + +You can use it anyway by ignoring its problems, using one of the +following methods: + +a) For `nixos-rebuild` you can add "warn" or "ignore" entries to + `nixpkgs.config.problems.handlers` inside configuration.nix, + like this: + + { + nixpkgs.config.problems.handlers = { + a.removal = "warn"; + }; + } + +b) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add + "warn" or "ignore" to `problems.handlers` in + ~/.config/nixpkgs/config.nix, like this: + + { + problems.handlers = { + a.removal = "warn"; + }; + } diff --git a/pkgs/test/problems/default.nix b/pkgs/test/problems/default.nix new file mode 100644 index 0000000000000..cd750df67ba9a --- /dev/null +++ b/pkgs/test/problems/default.nix @@ -0,0 +1,56 @@ +{ + lib, + nix, + runCommand, + removeReferencesTo, + path, +}: +let + nixpkgs = lib.cleanSource path; + unitResult = import ./unit.nix { inherit lib; }; +in +assert lib.assertMsg (unitResult == [ ]) + "problems/unit.nix failing: ${lib.generators.toPretty { } unitResult}"; +lib.mapAttrs ( + name: _: + let + nixFile = "${./cases + "/${name}/default.nix"}"; + result = runCommand "test-problems-${name}" { nativeBuildInputs = [ removeReferencesTo ]; } '' + export NIX_STATE_DIR=$(mktemp -d) + mkdir $out + + command=( + # FIXME: Using this version because it doesn't print a trace by default + # Probably should have some regex-style error matching instead + "${lib.getBin nix}/bin/nix-instantiate" + ${nixFile} + # Readonly mode because we don't need to write anything to the store + "--readonly-mode" + "--arg" + "nixpkgs" + "${nixpkgs}" + ) + + echo "''${command[*]@Q}" > $out/command + echo "Running ''${command[*]@Q}" + set +e + "''${command[@]}" >$out/stdout 2>$out/stderr + set +e + echo "$?" > $out/code + echo "Command exited with code $(<$out/code)" + remove-references-to -t ${nixFile} $out/* + if grep '(stack trace truncated.*)' $out/stderr; then + sed -i -n '/(stack trace truncated.*)/,$ p' $out/stderr + fi + sed -i 's/^ //' $out/stderr + ''; + checker = runCommand "test-problems-check-${name}" { } '' + if ! diff ${result}/stderr ${./cases + "/${name}/expected-stderr"}; then + echo "Output of $(< ${result}/command) does not match what was expected (${result}/stderr)" + exit 1 + fi + touch $out + ''; + in + checker +) (builtins.readDir ./cases) diff --git a/pkgs/test/problems/unit.nix b/pkgs/test/problems/unit.nix new file mode 100644 index 0000000000000..073ddaa12cfa5 --- /dev/null +++ b/pkgs/test/problems/unit.nix @@ -0,0 +1,153 @@ +{ lib }: +let + p = import ../../stdenv/generic/problems.nix { inherit lib; }; + + genHandlerTest = + let + slowReference = + config: package: name: kind: + # Try to find an explicit handler + (config.problems.handlers.${package} or { }).${name} + # Fall back, iterating through the matchers + or (lib.pipe config.problems.matchers [ + # Find matches + (lib.filter ( + matcher: + (matcher.name != null -> name == matcher.name) + && (matcher.kind != null -> kind == matcher.kind) + && (matcher.package != null -> package == matcher.package) + )) + # Extract handler level + (map (matcher: matcher.handler)) + # Take the strongest matched handler level + (lib.foldl' p.handlers.max "ignore") + ]); + + genValue = + f: + map + ( + package: + map + ( + name: + map (kind: f package name kind) [ + "k1" + "k2" + "k3" + ] + ) + [ + "n1" + "n2" + "n3" + ] + ) + [ + "p1" + "p2" + "p3" + ]; + + in + v: { + expr = genValue (p.genHandlerSwitch { problems = v; }).handlerForProblem; + expected = genValue (slowReference { + problems = v; + }); + }; +in +lib.runTests { + testHandlersLessThan = + let + levels = p.handlers.levels; + slowReference = + a: b: + lib.lists.findFirstIndex (v: v == a) (abort "Shouldn't happen") levels + < lib.lists.findFirstIndex (v: v == b) (abort "Shouldn't happen") levels; + + genValue = + f: + lib.genList ( + i: lib.genList (j: f (lib.elemAt levels i) (lib.elemAt levels j)) (lib.length levels) + ) (lib.length levels); + in + { + expr = genValue p.handlers.lessThan; + expected = genValue slowReference; + }; + + testHandlerEmpty = genHandlerTest { + matchers = [ ]; + handlers = { }; + }; + + testHandlerNameSpecificHandlers = genHandlerTest { + matchers = [ ]; + handlers.p1.n1 = "error"; + handlers.p1.n2 = "warn"; + handlers.p1.n3 = "ignore"; + }; + + testHandlerPackageSpecificHandlers = genHandlerTest { + matchers = [ ]; + handlers.p1.n1 = "error"; + handlers.p2.n1 = "warn"; + handlers.p3.n1 = "ignore"; + }; + + testHandlersOverrideMatchers = genHandlerTest { + matchers = [ + { + package = "p1"; + name = "n1"; + kind = null; + handler = "error"; + } + ]; + handlers.p1.n1 = "warn"; + }; + + testMatchersDefault = genHandlerTest { + matchers = [ + # Everything should warn by default + { + package = null; + name = null; + kind = null; + handler = "warn"; + } + ]; + handlers = { }; + }; + + testMatchersComplicated = genHandlerTest { + matchers = [ + { + package = "p1"; + name = null; + kind = null; + handler = "warn"; + } + { + package = "p1"; + name = "n1"; + kind = null; + handler = "error"; + } + { + package = "p1"; + name = null; + kind = "k1"; + handler = "error"; + } + { + package = "p1"; + name = "n2"; + kind = "k2"; + handler = "error"; + } + ]; + handlers = { }; + }; +} diff --git a/pkgs/top-level/config.nix b/pkgs/top-level/config.nix index c8e8a3f585908..2ae32d4d1c459 100644 --- a/pkgs/top-level/config.nix +++ b/pkgs/top-level/config.nix @@ -45,11 +45,18 @@ let internal = true; }; + # Should be replaced by importing in the future + # see also https://github.com/NixOS/nixpkgs/pull/207187 warnings = mkOption { type = types.listOf types.str; default = [ ]; internal = true; }; + assertions = mkOption { + type = types.listOf types.anything; + default = [ ]; + internal = true; + }; # Config options @@ -298,6 +305,7 @@ let feature = "build packages with ROCm support by default"; }; + # TODO: Deprecate showDerivationWarnings = mkOption { type = types.listOf (types.enum [ "maintainerless" ]); default = [ ]; @@ -366,6 +374,8 @@ let Please read https://www.visualstudio.com/license-terms/mt644918/ and enable this config if you accept. ''; }; + + problems = (import ../stdenv/generic/problems.nix { inherit lib; }).configOptions; }; in @@ -391,6 +401,24 @@ in warnings = optionals config.warnUndeclaredOptions ( mapAttrsToList (k: v: "undeclared Nixpkgs option set: config.${k}") config._undeclared or { } ); + + assertions = + # Collect the assertions from the problems.matchers.* submodules, propagate them into here + lib.concatMap (matcher: matcher.assertions) config.problems.matchers; + + # Put the default value for matchers in here (as in, not as an *actual* mkDefault default value), + # to force it being merged with any custom values instead of being overridden. + problems.matchers = [ + # Be loud and clear about package removals + { + kind = "removal"; + handler = "error"; + } + (lib.mkIf (lib.elem "maintainerless" config.showDerivationWarnings) { + kind = "maintainerless"; + handler = "warn"; + }) + ]; }; } diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index ecfef757909e6..3333d5d92331c 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -126,7 +126,14 @@ let }; # take all the rest as-is - config = lib.showWarnings configEval.config.warnings configEval.config; + config = + let + failedAssertions = map (x: x.message) (lib.filter (x: !x.assertion) configEval.config.assertions); + in + if failedAssertions != [ ] then + throw "\nFailed assertions:\n${lib.concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}" + else + lib.showWarnings configEval.config.warnings configEval.config; # A few packages make a new package set to draw their dependencies from. # (Currently to get a cross tool chain, or forced-i686 package.) Rather than