summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/generators.nix59
-rw-r--r--lib/licenses.nix6
-rw-r--r--lib/modules.nix14
-rw-r--r--lib/options.nix34
-rw-r--r--lib/systems/doubles.nix53
-rw-r--r--lib/tests/misc.nix72
-rwxr-xr-xlib/tests/modules.sh53
-rw-r--r--lib/tests/modules/types-anything/attrs-coercible.nix12
-rw-r--r--lib/tests/modules/types-anything/equal-atoms.nix26
-rw-r--r--lib/tests/modules/types-anything/functions.nix17
-rw-r--r--lib/tests/modules/types-anything/lists.nix16
-rw-r--r--lib/tests/modules/types-anything/mk-mods.nix44
-rw-r--r--lib/tests/modules/types-anything/nested-attrs.nix22
-rw-r--r--lib/types.nix55
14 files changed, 399 insertions, 84 deletions
diff --git a/lib/generators.nix b/lib/generators.nix
index abd237eb7d37..501a23599f45 100644
--- a/lib/generators.nix
+++ b/lib/generators.nix
@@ -203,40 +203,59 @@ rec {
/* If this option is true, attrsets like { __pretty = fn; val = …; }
will use fn to convert val to a pretty printed representation.
(This means fn is type Val -> String.) */
- allowPrettyValues ? false
- }@args: v: with builtins;
+ allowPrettyValues ? false,
+ /* If this option is true, the output is indented with newlines for attribute sets and lists */
+ multiline ? true
+ }@args: let
+ go = indent: v: with builtins;
let isPath = v: typeOf v == "path";
+ introSpace = if multiline then "\n${indent} " else " ";
+ outroSpace = if multiline then "\n${indent}" else " ";
in if isInt v then toString v
else if isFloat v then "~${toString v}"
- else if isString v then ''"${libStr.escape [''"''] v}"''
+ else if isString v then
+ let
+ # Separate a string into its lines
+ newlineSplits = filter (v: ! isList v) (builtins.split "\n" v);
+ # For a '' string terminated by a \n, which happens when the closing '' is on a new line
+ multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''";
+ # For a '' string not terminated by a \n, which happens when the closing '' is not on a new line
+ multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''";
+ # For single lines, replace all newlines with their escaped representation
+ singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\"";
+ in if multiline && length newlineSplits > 1 then
+ if lib.last newlineSplits == "" then multilineResult else multilineResult'
+ else singlelineResult
else if true == v then "true"
else if false == v then "false"
else if null == v then "null"
else if isPath v then toString v
- else if isList v then "[ "
- + libStr.concatMapStringsSep " " (toPretty args) v
- + " ]"
+ else if isList v then
+ if v == [] then "[ ]"
+ else "[" + introSpace
+ + libStr.concatMapStringsSep introSpace (go (indent + " ")) v
+ + outroSpace + "]"
+ else if isFunction v then
+ let fna = lib.functionArgs v;
+ showFnas = concatStringsSep ", " (libAttr.mapAttrsToList
+ (name: hasDefVal: if hasDefVal then name + "?" else name)
+ fna);
+ in if fna == {} then "<function>"
+ else "<function, args: {${showFnas}}>"
else if isAttrs v then
# apply pretty values if allowed
if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
then v.__pretty v.val
- # TODO: there is probably a better representation?
+ else if v == {} then "{ }"
else if v ? type && v.type == "derivation" then
- "<δ:${v.name}>"
- # "<δ:${concatStringsSep "," (builtins.attrNames v)}>"
- else "{ "
- + libStr.concatStringsSep " " (libAttr.mapAttrsToList
+ "<derivation ${v.drvPath}>"
+ else "{" + introSpace
+ + libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
(name: value:
- "${toPretty args name} = ${toPretty args value};") v)
- + " }"
- else if isFunction v then
- let fna = lib.functionArgs v;
- showFnas = concatStringsSep "," (libAttr.mapAttrsToList
- (name: hasDefVal: if hasDefVal then "(${name})" else name)
- fna);
- in if fna == {} then "<λ>"
- else "<λ:{${showFnas}}>"
+ "${libStr.escapeNixIdentifier name} = ${go (indent + " ") value};") v)
+ + outroSpace + "}"
else abort "generators.toPretty: should never happen (v = ${v})";
+ in go "";
# PLIST handling
toPlist = {}: v: let
diff --git a/lib/licenses.nix b/lib/licenses.nix
index 8492cf2495b4..a704a6884c7d 100644
--- a/lib/licenses.nix
+++ b/lib/licenses.nix
@@ -644,6 +644,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
url = "https://enterprise.dejacode.com/licenses/public/purdue-bsd";
};
+ prosperity30 = {
+ fullName = "Prosperity-3.0.0";
+ free = false;
+ url = "https://prosperitylicense.com/versions/3.0.0.html";
+ };
+
qhull = spdx {
spdxId = "Qhull";
fullName = "Qhull License";
diff --git a/lib/modules.nix b/lib/modules.nix
index e7012742a982..df3a2ad17e5f 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -117,7 +117,7 @@ rec {
if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
let
firstDef = head merged.unmatchedDefns;
- baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' defined in `${firstDef.file}' does not exist.";
+ baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}";
in
if attrNames options == [ "_module" ]
then throw ''
@@ -449,7 +449,13 @@ rec {
# Handle properties, check types, and merge everything together.
res =
if opt.readOnly or false && length defs' > 1 then
- throw "The option `${showOption loc}' is read-only, but it's set multiple times."
+ let
+ # For a better error message, evaluate all readOnly definitions as
+ # if they were the only definition.
+ separateDefs = map (def: def // {
+ value = (mergeDefinitions loc opt.type [ def ]).mergedValue;
+ }) defs';
+ in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
else
mergeDefinitions loc opt.type defs';
@@ -497,8 +503,8 @@ rec {
mergedValue =
if isDefined then
if all (def: type.check def.value) defsFinal then type.merge loc defsFinal
- else let firstInvalid = findFirst (def: ! type.check def.value) null defsFinal;
- in throw "The option value `${showOption loc}' in `${firstInvalid.file}' is not of type `${type.description}'."
+ else let allInvalid = filter (def: ! type.check def.value) defsFinal;
+ in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
else
# (nixos-option detects this specific error message and gives it special
# handling. If changed here, please change it there too.)
diff --git a/lib/options.nix b/lib/options.nix
index 38f4f1329f21..5b7482c80937 100644
--- a/lib/options.nix
+++ b/lib/options.nix
@@ -96,22 +96,26 @@ rec {
else if all isBool list then foldl' lib.or false list
else if all isString list then lib.concatStrings list
else if all isInt list && all (x: x == head list) list then head list
- else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
+ else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
mergeOneOption = loc: defs:
if defs == [] then abort "This case should never happen."
else if length defs != 1 then
- throw "The unique option `${showOption loc}' is defined multiple times, in:\n - ${concatStringsSep "\n - " (getFiles defs)}."
+ throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}"
else (head defs).value;
/* "Merge" option definitions by checking that they all have the same value. */
mergeEqualOption = loc: defs:
if defs == [] then abort "This case should never happen."
- else foldl' (val: def:
- if def.value != val then
- throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
+ # Return early if we only have one element
+ # This also makes it work for functions, because the foldl' below would try
+ # to compare the first element with itself, which is false for functions
+ else if length defs == 1 then (elemAt defs 0).value
+ else (foldl' (first: def:
+ if def.value != first.value then
+ throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}"
else
- val) (head defs).value defs;
+ first) (head defs) defs).value;
/* Extracts values of all "value" keys of the given list.
@@ -209,6 +213,24 @@ rec {
else escaped;
in (concatStringsSep ".") (map escapeOptionPart parts);
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
+
+ showDefs = defs: concatMapStrings (def:
+ let
+ # Pretty print the value for display, if successful
+ prettyEval = builtins.tryEval (lib.generators.toPretty {} def.value);
+ # Split it into its lines
+ lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value);
+ # Only display the first 5 lines, and indent them for better visibility
+ value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
+ result =
+ # Don't print any value if evaluating the value strictly fails
+ if ! prettyEval.success then ""
+ # Put it on a new line if it consists of multiple
+ else if length lines > 1 then ":\n " + value
+ else ": " + value;
+ in "\n- In `${def.file}'${result}"
+ ) defs;
+
unknownModule = "<unknown-file>";
}
diff --git a/lib/systems/doubles.nix b/lib/systems/doubles.nix
index fb7d722e737e..517a7296afd2 100644
--- a/lib/systems/doubles.nix
+++ b/lib/systems/doubles.nix
@@ -50,32 +50,35 @@ in {
none = [];
- arm = filterDoubles predicates.isAarch32;
- aarch64 = filterDoubles predicates.isAarch64;
- x86 = filterDoubles predicates.isx86;
- i686 = filterDoubles predicates.isi686;
- x86_64 = filterDoubles predicates.isx86_64;
- mips = filterDoubles predicates.isMips;
- riscv = filterDoubles predicates.isRiscV;
- vc4 = filterDoubles predicates.isVc4;
- js = filterDoubles predicates.isJavaScript;
-
- cygwin = filterDoubles predicates.isCygwin;
- darwin = filterDoubles predicates.isDarwin;
- freebsd = filterDoubles predicates.isFreeBSD;
+ arm = filterDoubles predicates.isAarch32;
+ aarch64 = filterDoubles predicates.isAarch64;
+ x86 = filterDoubles predicates.isx86;
+ i686 = filterDoubles predicates.isi686;
+ x86_64 = filterDoubles predicates.isx86_64;
+ mips = filterDoubles predicates.isMips;
+ riscv = filterDoubles predicates.isRiscV;
+ vc4 = filterDoubles predicates.isVc4;
+ js = filterDoubles predicates.isJavaScript;
+
+ bigEndian = filterDoubles predicates.isBigEndian;
+ littleEndian = filterDoubles predicates.isLittleEndian;
+
+ cygwin = filterDoubles predicates.isCygwin;
+ darwin = filterDoubles predicates.isDarwin;
+ freebsd = filterDoubles predicates.isFreeBSD;
# Should be better, but MinGW is unclear.
- gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; });
- illumos = filterDoubles predicates.isSunOS;
- linux = filterDoubles predicates.isLinux;
- netbsd = filterDoubles predicates.isNetBSD;
- openbsd = filterDoubles predicates.isOpenBSD;
- unix = filterDoubles predicates.isUnix;
- wasi = filterDoubles predicates.isWasi;
- redox = filterDoubles predicates.isRedox;
- windows = filterDoubles predicates.isWindows;
- genode = filterDoubles predicates.isGenode;
-
- embedded = filterDoubles predicates.isNone;
+ gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; });
+ illumos = filterDoubles predicates.isSunOS;
+ linux = filterDoubles predicates.isLinux;
+ netbsd = filterDoubles predicates.isNetBSD;
+ openbsd = filterDoubles predicates.isOpenBSD;
+ unix = filterDoubles predicates.isUnix;
+ wasi = filterDoubles predicates.isWasi;
+ redox = filterDoubles predicates.isRedox;
+ windows = filterDoubles predicates.isWindows;
+ genode = filterDoubles predicates.isGenode;
+
+ embedded = filterDoubles predicates.isNone;
mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "armv7a-linux" "aarch64-linux" "powerpc64le-linux"];
}
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 03eff4ce48b7..3a6db53c276d 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -445,32 +445,90 @@ runTests {
expected = builtins.toJSON val;
};
- testToPretty = {
- expr = mapAttrs (const (generators.toPretty {})) rec {
+ testToPretty =
+ let
+ deriv = derivation { name = "test"; builder = "/bin/sh"; system = builtins.currentSystem; };
+ in {
+ expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec {
int = 42;
float = 0.1337;
bool = true;
+ emptystring = "";
string = ''fno"rd'';
+ newlinestring = "\n";
path = /. + "/foo";
null_ = null;
function = x: x;
functionArgs = { arg ? 4, foo }: arg;
list = [ 3 4 function [ false ] ];
+ emptylist = [];
attrs = { foo = null; "foo bar" = "baz"; };
- drv = derivation { name = "test"; system = builtins.currentSystem; };
+ emptyattrs = {};
+ drv = deriv;
};
expected = rec {
int = "42";
float = "~0.133700";
bool = "true";
+ emptystring = ''""'';
string = ''"fno\"rd"'';
+ newlinestring = "\"\\n\"";
path = "/foo";
null_ = "null";
- function = "<λ>";
- functionArgs = "<λ:{(arg),foo}>";
+ function = "<function>";
+ functionArgs = "<function, args: {arg?, foo}>";
list = "[ 3 4 ${function} [ false ] ]";
- attrs = "{ \"foo\" = null; \"foo bar\" = \"baz\"; }";
- drv = "<δ:test>";
+ emptylist = "[ ]";
+ attrs = "{ foo = null; \"foo bar\" = \"baz\"; }";
+ emptyattrs = "{ }";
+ drv = "<derivation ${deriv.drvPath}>";
+ };
+ };
+
+ testToPrettyMultiline = {
+ expr = mapAttrs (const (generators.toPretty { })) rec {
+ list = [ 3 4 [ false ] ];
+ attrs = { foo = null; bar.foo = "baz"; };
+ newlinestring = "\n";
+ multilinestring = ''
+ hello
+ there
+ test
+ '';
+ multilinestring' = ''
+ hello
+ there
+ test'';
+ };
+ expected = rec {
+ list = ''
+ [
+ 3
+ 4
+ [
+ false
+ ]
+ ]'';
+ attrs = ''
+ {
+ bar = {
+ foo = "baz";
+ };
+ foo = null;
+ }'';
+ newlinestring = "''\n \n''";
+ multilinestring = ''
+ '''
+ hello
+ there
+ test
+ ''''';
+ multilinestring' = ''
+ '''
+ hello
+ there
+ test''''';
+
};
};
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 943deebe3c09..309c5311361c 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -49,7 +49,7 @@ checkConfigError() {
reportFailure "$@"
return 1
else
- if echo "$err" | grep --silent "$errorContains" ; then
+ if echo "$err" | grep -zP --silent "$errorContains" ; then
pass=$((pass + 1))
return 0;
else
@@ -62,17 +62,17 @@ checkConfigError() {
# Check boolean option.
checkConfigOutput "false" config.enable ./declare-enable.nix
-checkConfigError 'The option .* defined in .* does not exist.' config.enable ./define-enable.nix
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./define-enable.nix
# Check integer types.
# unsigned
checkConfigOutput "42" config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix
-checkConfigError 'The option value .* in .* is not of type.*unsigned integer.*' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
+checkConfigError 'A definition for option .* is not of type.*unsigned integer.*. Definition values:\n- In .*: -23' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix
# positive
-checkConfigError 'The option value .* in .* is not of type.*positive integer.*' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
+checkConfigError 'A definition for option .* is not of type.*positive integer.*. Definition values:\n- In .*: 0' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix
# between
checkConfigOutput "42" config.value ./declare-int-between-value.nix ./define-value-int-positive.nix
-checkConfigError 'The option value .* in .* is not of type.*between.*-21 and 43.*inclusive.*' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
+checkConfigError 'A definition for option .* is not of type.*between.*-21 and 43.*inclusive.*. Definition values:\n- In .*: -23' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix
# Check either types
# types.either
@@ -125,7 +125,7 @@ checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-foo-enable
set -- config.enable ./define-enable.nix ./declare-enable.nix
checkConfigOutput "true" "$@"
checkConfigOutput "false" "$@" ./disable-define-enable.nix
-checkConfigError "The option .*enable.* defined in .* does not exist" "$@" ./disable-declare-enable.nix
+checkConfigError "The option .*enable.* does not exist. Definition values:\n- In .*: true" "$@" ./disable-declare-enable.nix
checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix
checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix
@@ -142,17 +142,17 @@ checkConfigError 'infinite recursion encountered' "$@"
# Check _module.check.
set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix
-checkConfigError 'The option .* defined in .* does not exist.' "$@"
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*' "$@"
checkConfigOutput "true" "$@" ./define-module-check.nix
# Check coerced value.
checkConfigOutput "\"42\"" config.value ./declare-coerced-value.nix
checkConfigOutput "\"24\"" config.value ./declare-coerced-value.nix ./define-value-string.nix
-checkConfigError 'The option value .* in .* is not.*string or signed integer convertible to it' config.value ./declare-coerced-value.nix ./define-value-list.nix
+checkConfigError 'A definition for option .* is not.*string or signed integer convertible to it.*. Definition values:\n- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix
# Check coerced value with unsound coercion
checkConfigOutput "12" config.value ./declare-coerced-value-unsound.nix
-checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
+checkConfigError 'A definition for option .* is not of type .*. Definition values:\n- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
# Check mkAliasOptionModule.
@@ -183,7 +183,7 @@ checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.ni
checkConfigOutput "true" config.enable ./disable-recursive/main.nix
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo.nix}
checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix}
-checkConfigError 'The option .* defined in .* does not exist' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
+checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix}
# Check that imports can depend on derivations
checkConfigOutput "true" config.enable ./import-from-store.nix
@@ -207,7 +207,7 @@ checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-c
# Even with multiple assignments, a type error should be thrown if any of them aren't valid
-checkConfigError 'The option value .* in .* is not of type .*' \
+checkConfigError 'A definition for option .* is not of type .*' \
config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix
## Freeform modules
@@ -216,7 +216,7 @@ checkConfigOutput 24 config.value ./freeform-attrsOf.nix ./define-value-string.n
# No freeform assigments shouldn't make it error
checkConfigOutput '{ }' config ./freeform-attrsOf.nix
# but only if the type matches
-checkConfigError 'The option value .* in .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix
+checkConfigError 'A definition for option .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix
# and properties should be applied
checkConfigOutput yes config.value ./freeform-attrsOf.nix ./define-value-string-properties.nix
# Options should still be declarable, and be able to have a type that doesn't match the freeform type
@@ -233,6 +233,35 @@ checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.
checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix
checkConfigOutput 24 config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix
+## types.anything
+# Check that attribute sets are merged recursively
+checkConfigOutput null config.value.foo ./types-anything/nested-attrs.nix
+checkConfigOutput null config.value.l1.foo ./types-anything/nested-attrs.nix
+checkConfigOutput null config.value.l1.l2.foo ./types-anything/nested-attrs.nix
+checkConfigOutput null config.value.l1.l2.l3.foo ./types-anything/nested-attrs.nix
+# Attribute sets that are coercible to strings shouldn't be recursed into
+checkConfigOutput foo config.value.outPath ./types-anything/attrs-coercible.nix
+# Multiple lists aren't concatenated together
+checkConfigError 'The option .* has conflicting definitions' config.value ./types-anything/lists.nix
+# Check that all equalizable atoms can be used as long as all definitions are equal
+checkConfigOutput 0 config.value.int ./types-anything/equal-atoms.nix
+checkConfigOutput false config.value.bool ./types-anything/equal-atoms.nix
+checkConfigOutput '""' config.value.string ./types-anything/equal-atoms.nix
+checkConfigOutput / config.value.path ./types-anything/equal-atoms.nix
+checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix
+checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix
+# Functions can't be merged together
+checkConfigError "The option .* has conflicting definition values" config.value.multiple-lambdas ./types-anything/functions.nix
+checkConfigOutput '<LAMBDA>' config.value.single-lambda ./types-anything/functions.nix
+# Check that all mk* modifiers are applied
+checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix
+checkConfigOutput '{ }' config.value.mkiftrue ./types-anything/mk-mods.nix
+checkConfigOutput 1 config.value.mkdefault ./types-anything/mk-mods.nix
+checkConfigOutput '{ }' config.value.mkmerge ./types-anything/mk-mods.nix
+checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix
+checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix
+checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
+
cat <<EOF
====== module tests ======
$pass Pass
diff --git a/lib/tests/modules/types-anything/attrs-coercible.nix b/lib/tests/modules/types-anything/attrs-coercible.nix
new file mode 100644
index 000000000000..085cbd638f17
--- /dev/null
+++ b/lib/tests/modules/types-anything/attrs-coercible.nix
@@ -0,0 +1,12 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config.value = {
+ outPath = "foo";
+ err = throw "err";
+ };
+
+}
diff --git a/lib/tests/modules/types-anything/equal-atoms.nix b/lib/tests/modules/types-anything/equal-atoms.nix
new file mode 100644
index 000000000000..972711201a09
--- /dev/null
+++ b/lib/tests/modules/types-anything/equal-atoms.nix
@@ -0,0 +1,26 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.int = 0;
+ value.bool = false;
+ value.string = "";
+ value.path = /.;
+ value.null = null;
+ value.float = 0.1;
+ }
+ {
+ value.int = 0;
+ value.bool = false;
+ value.string = "";
+ value.path = /.;
+ value.null = null;
+ value.float = 0.1;
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/functions.nix b/lib/tests/modules/types-anything/functions.nix
new file mode 100644
index 000000000000..079518913918
--- /dev/null
+++ b/lib/tests/modules/types-anything/functions.nix
@@ -0,0 +1,17 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.single-lambda = x: x;
+ value.multiple-lambdas = x: x;
+ }
+ {
+ value.multiple-lambdas = x: x;
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/lists.nix b/lib/tests/modules/types-anything/lists.nix
new file mode 100644
index 000000000000..bd846afd3d18
--- /dev/null
+++ b/lib/tests/modules/types-anything/lists.nix
@@ -0,0 +1,16 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value = [ null ];
+ }
+ {
+ value = [ null ];
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/mk-mods.nix b/lib/tests/modules/types-anything/mk-mods.nix
new file mode 100644
index 000000000000..f84ad01df017
--- /dev/null
+++ b/lib/tests/modules/types-anything/mk-mods.nix
@@ -0,0 +1,44 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.mkiffalse = lib.mkIf false {};
+ }
+ {
+ value.mkiftrue = lib.mkIf true {};
+ }
+ {
+ value.mkdefault = lib.mkDefault 0;
+ }
+ {
+ value.mkdefault = 1;
+ }
+ {
+ value.mkmerge = lib.mkMerge [
+ {}
+ ];
+ }
+ {
+ value.mkbefore = lib.mkBefore true;
+ }
+ {
+ value.nested = lib.mkMerge [
+ {
+ foo = lib.mkDefault 0;
+ bar = lib.mkIf false 0;
+ }
+ (lib.mkIf true {
+ foo = lib.mkIf true (lib.mkForce 1);
+ bar = {
+ baz = lib.mkDefault "baz";
+ };
+ })
+ ];
+ }
+ ];
+
+}
diff --git a/lib/tests/modules/types-anything/nested-attrs.nix b/lib/tests/modules/types-anything/nested-attrs.nix
new file mode 100644
index 000000000000..e57d33ef8717
--- /dev/null
+++ b/lib/tests/modules/types-anything/nested-attrs.nix
@@ -0,0 +1,22 @@
+{ lib, ... }: {
+
+ options.value = lib.mkOption {
+ type = lib.types.anything;
+ };
+
+ config = lib.mkMerge [
+ {
+ value.foo = null;
+ }
+ {
+ value.l1.foo = null;
+ }
+ {
+ value.l1.l2.foo = null;
+ }
+ {
+ value.l1.l2.l3.foo = null;
+ }
+ ];
+
+}
diff --git a/lib/types.nix b/lib/types.nix
index ef2c78082f8d..e9e45dc25c72 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -104,6 +104,42 @@ rec {
# When adding new types don't forget to document them in
# nixos/doc/manual/development/option-types.xml!
types = rec {
+
+ anything = mkOptionType {
+ name = "anything";
+ description = "anything";
+ check = value: true;
+ merge = loc: defs:
+ let
+ getType = value:
+ if isAttrs value && isCoercibleToString value
+ then "stringCoercibleSet"
+ else builtins.typeOf value;
+
+ # Returns the common type of all definitions, throws an error if they
+ # don't have the same type
+ commonType = foldl' (type: def:
+ if getType def.value == type
+ then type
+ else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
+ ) (getType (head defs).value) defs;
+
+ mergeFunction = {
+ # Recursively merge attribute sets
+ set = (attrsOf anything).merge;
+ # Safe and deterministic behavior for lists is to only accept one definition
+ # listOf only used to apply mkIf and co.
+ list =
+ if length defs > 1
+ then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
+ else (listOf anything).merge;
+ # This is the type of packages, only accept a single definition
+ stringCoercibleSet = mergeOneOption;
+ # Otherwise fall back to only allowing all equal definitions
+ }.${commonType} or mergeEqualOption;
+ in mergeFunction loc defs;
+ };
+
unspecified = mkOptionType {
name = "unspecified";
};
@@ -263,16 +299,14 @@ rec {
check = isList;
merge = loc: defs:
map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
- if isList def.value then
- imap1 (m: def':
- (mergeDefinitions
- (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
- elemType
- [{ inherit (def) file; value = def'; }]
- ).optionalValue
- ) def.value
- else
- throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
+ imap1 (m: def':
+ (mergeDefinitions
+ (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
+ elemType
+ [{ inherit (def) file; value = def'; }]
+ ).optionalValue
+ ) def.value
+ ) defs)));
emptyValue = { value = {}; };
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
getSubModules = elemType.getSubModules;
@@ -465,6 +499,7 @@ rec {
show = v:
if builtins.isString v then ''"${v}"''
else if builtins.isInt v then builtins.toString v
+ else if builtins.isBool v then if v then "true" else "false"
else ''<${builtins.typeOf v}>'';
in
mkOptionType rec {