diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/modules.nix | 14 | ||||
-rw-r--r-- | lib/options.nix | 30 | ||||
-rwxr-xr-x | lib/tests/modules.sh | 26 | ||||
-rw-r--r-- | lib/types.nix | 18 |
4 files changed, 55 insertions, 33 deletions
diff --git a/lib/modules.nix b/lib/modules.nix index 412c7f1df712f..02a669df65938 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 0494a597ab806..5b7482c80937a 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -96,12 +96,12 @@ 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. */ @@ -111,11 +111,11 @@ rec { # 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' (val: def: - if def.value != val then - throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." + 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. @@ -213,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/tests/modules.sh b/lib/tests/modules.sh index cfe474d4ded25..309c5311361c1 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 @@ -251,7 +251,7 @@ 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 definitions" config.value.multiple-lambdas ./types-anything/functions.nix +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 diff --git a/lib/types.nix b/lib/types.nix index aae45366b8fbc..77105740bc235 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -299,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; |