Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos: use structured attrs for assertions/warnings #342372

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
57 changes: 57 additions & 0 deletions lib/attrsets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,63 @@ rec {
else
[];

/**
Recursively collect sets that verify a given predicate named `pred`
from the set `attrs`. The recursion is stopped when the predicate is
verified. This version of `collect` also collects the attribute paths
of the items.


# Inputs

`pred`

: Given an attribute's value, determine if recursion should stop.

`attrs`

: The attribute set to recursively collect.

# Type

```
collect' :: (AttrSet -> Bool) -> AttrSet -> [{ path :: [ String ], value :: x }]
```

# Examples
:::{.example}
## `lib.attrsets.collect'` usage example

```nix
collect' isList { a = { b = ["b"]; }; c = [1]; }
=> [
{ path = [ "a" "b" ]; value = [ "b" ];
{ path = [ "c" ]; value = [ 1 ]; }
]

collect (x: x ? outPath)
{ a = { outPath = "a/"; }; b = { outPath = "b/"; }; }
=> [
{ path = [ "a" ]; value = { outPath = "a/"; }; }
{ path = [ "b" ]; value = { outPath = "b/"; }; }
]
```

:::
*/
collect' = let
collect'' =
path:
pred:
value:
if pred value then
[ { inherit path value; } ]
else if isAttrs value then
concatMap ({ name, value }: collect'' (path ++ [ name ]) pred value) (attrsToList value)
else
[];
in collect'' [];

/**
Return the cartesian product of attribute set value combinations.

Expand Down
2 changes: 1 addition & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ let
toExtension;
inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath
getAttrFromPath attrVals attrNames attrValues getAttrs catAttrs filterAttrs
filterAttrsRecursive foldlAttrs foldAttrs collect nameValuePair mapAttrs
filterAttrsRecursive foldlAttrs foldAttrs collect collect' nameValuePair mapAttrs
mapAttrs' mapAttrsToList attrsToList concatMapAttrs mapAttrsRecursive
mapAttrsRecursiveCond genAttrs isDerivation toDerivation optionalAttrs
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
Expand Down
38 changes: 20 additions & 18 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1106,14 +1106,13 @@ let
visible = false;
apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}";
});
config.assertions =
let opt = getAttrFromPath optionName options; in [{
assertion = !opt.isDefined;
message = ''
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
${replacementInstructions}
'';
}];
config.assertions = setAttrByPath (optionName ++ [ "removed" ]) (let opt = getAttrFromPath optionName options; in {
assertion = !opt.isDefined;
message = ''
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
${replacementInstructions}
'';
});
};

/* Return a module that causes a warning to be shown if the
Expand Down Expand Up @@ -1194,14 +1193,15 @@ let
})) from);

config = {
warnings = filter (x: x != "") (map (f:
let val = getAttrFromPath f config;
opt = getAttrFromPath f options;
in
optionalString
(val != "_mkMergedOptionModule")
"The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly."
) from);
warnings = foldl' recursiveUpdate {} (map (path: setAttrByPath (path ++ ["mergedOption"]) {
condition = (getAttrFromPath path config) != "_mkMergedOptionModule";
message = let
opt = getAttrFromPath path options;
in ''
The option `${showOption path}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type.
Please read `${showOption to}' documentation and update your configuration accordingly.
'';
}) from);
} // setAttrByPath to (mkMerge
(optional
(any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
Expand Down Expand Up @@ -1357,8 +1357,10 @@ let
});
config = mkIf condition (mkMerge [
(optionalAttrs (options ? warnings) {
warnings = optional (warn && fromOpt.isDefined)
"The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
warnings = setAttrByPath (from ++ [ "aliased" ]) {
condition = warn && fromOpt.isDefined;
message = "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
};
})
(if withPriority
then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt
Expand Down
42 changes: 42 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ let
callPackageWith
cartesianProduct
cli
collect
collect'
composeExtensions
composeManyExtensions
concatLines
Expand Down Expand Up @@ -235,6 +237,46 @@ runTests {
];
};

testCollect = {
expr = [
(collect (x: x ? special) { a.b.c.special = true; x.y.z.special = false; })
(collect (x: x == 1) { a = 1; b = 2; c = 3; d.inner = 1; })
];
expected = [
[ { special = true; } { special = false; } ]
[ 1 1 ]
];
};

testCollect' = {
expr = [
(collect' (x: x ? special) { a.b.c.special = true; x.y.z.special = false; })
(collect' (x: x == 1) { a = 1; b = 2; c = 3; d.inner = 1; })
];
expected = [
[
{
path = [ "a" "b" "c" ];
value = { special = true; };
}
{
path = [ "x" "y" "z" ];
value = { special = false; };
}
]
[
{
path = [ "a" ];
value = 1;
}
{
path = [ "d" "inner" ];
value = 1;
}
]
];
};

testComposeExtensions = {
expr = let obj = makeExtensible (self: { foo = self.bar; });
f = self: super: { bar = false; baz = true; };
Expand Down
17 changes: 15 additions & 2 deletions lib/tests/modules/doRename-warnings.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@
(lib.doRename { from = ["a" "b"]; to = ["c" "d" "e"]; warn = true; use = x: x; visible = true; })
];
options = {
warnings = lib.mkOption { type = lib.types.listOf lib.types.str; };
warnings = lib.mkOption {
type = let
checkedWarningItemType = let
check = x: x ? condition && x ? message;
in lib.types.addCheck (lib.types.attrsOf lib.types.anything) check;

nestedWarningAttrsType = let
nestedWarningItemType = lib.types.either checkedWarningItemType (lib.types.attrsOf nestedWarningItemType);
in nestedWarningItemType;
in lib.types.submodule { freeformType = nestedWarningAttrsType; };
};

c.d.e = lib.mkOption {};
result = lib.mkOption {};
};
config = {
a.b = 1234;
result = lib.concatStringsSep "%" config.warnings;
result = let
warning = config.warnings.a.b.aliased;
in lib.optionalString warning.condition warning.message;
};
}
22 changes: 11 additions & 11 deletions nixos/doc/manual/development/assertions.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ This is an example of using `warnings`.
{ config, lib, ... }:
{
config = lib.mkIf config.services.foo.enable {
warnings =
if config.services.foo.bar
then [ ''You have enabled the bar feature of the foo service.
This is known to cause some specific problems in certain situations.
'' ]
else [];
warnings.services.foo.beCarefulWithBar = {
condition = config.services.foo.bar;
message = ''
You have enabled the bar feature of the foo service.
This is known to cause some specific problems in certain situations.
'';
};
};
}
```
Expand All @@ -30,11 +31,10 @@ This example, extracted from the [`syslogd` module](https://github.com/NixOS/nix
{ config, lib, ... }:
{
config = lib.mkIf config.services.syslogd.enable {
assertions =
[ { assertion = !config.services.rsyslogd.enable;
message = "rsyslogd conflicts with syslogd";
}
];
assertions.services.syslogd.rsyslogdConflict = {
assertion = !config.services.rsyslogd.enable;
message = "rsyslogd conflicts with syslogd";
};
};
}
```
Loading