diff options
author | Frederik Rietdijk <fridh@fridh.nl> | 2020-09-22 18:31:56 +0200 |
---|---|---|
committer | Frederik Rietdijk <fridh@fridh.nl> | 2020-09-22 18:31:56 +0200 |
commit | 7bff759fac26f1312d08e78bb0f654139b96e798 (patch) | |
tree | 06667a122322d925261f6a7c032f4cfd962d28d8 /lib | |
parent | c24b9331e038e49199abf887e966bd0c6b7b96a0 (diff) | |
parent | e3607652b6dd8cba9d1501cbf44b975a7b159cfa (diff) |
Merge staging-next into staging
Diffstat (limited to 'lib')
-rw-r--r-- | lib/generators.nix | 59 | ||||
-rw-r--r-- | lib/options.nix | 4 | ||||
-rw-r--r-- | lib/tests/misc.nix | 72 | ||||
-rwxr-xr-x | lib/tests/modules.sh | 29 | ||||
-rw-r--r-- | lib/tests/modules/types-anything/attrs-coercible.nix | 12 | ||||
-rw-r--r-- | lib/tests/modules/types-anything/equal-atoms.nix | 26 | ||||
-rw-r--r-- | lib/tests/modules/types-anything/functions.nix | 17 | ||||
-rw-r--r-- | lib/tests/modules/types-anything/lists.nix | 16 | ||||
-rw-r--r-- | lib/tests/modules/types-anything/mk-mods.nix | 44 | ||||
-rw-r--r-- | lib/tests/modules/types-anything/nested-attrs.nix | 22 | ||||
-rw-r--r-- | lib/types.nix | 36 |
11 files changed, 310 insertions, 27 deletions
diff --git a/lib/generators.nix b/lib/generators.nix index abd237eb7d377..501a23599f45e 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/options.nix b/lib/options.nix index 38f4f1329f212..0494a597ab806 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -107,6 +107,10 @@ rec { /* "Merge" option definitions by checking that they all have the same value. */ mergeEqualOption = loc: defs: if defs == [] then abort "This case should never happen." + # 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' (val: def: if def.value != val then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 03eff4ce48b7c..3a6db53c276d2 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 943deebe3c093..cfe474d4ded25 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -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 definitions" 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 0000000000000..085cbd638f17f --- /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 0000000000000..972711201a095 --- /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 0000000000000..0795189139189 --- /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 0000000000000..bd846afd3d185 --- /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 0000000000000..f84ad01df0174 --- /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 0000000000000..e57d33ef8717e --- /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 ef2c78082f8d6..aae45366b8fbc 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"; }; |