summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorFrederik Rietdijk <fridh@fridh.nl>2020-09-22 18:31:56 +0200
committerFrederik Rietdijk <fridh@fridh.nl>2020-09-22 18:31:56 +0200
commit7bff759fac26f1312d08e78bb0f654139b96e798 (patch)
tree06667a122322d925261f6a7c032f4cfd962d28d8 /lib
parentc24b9331e038e49199abf887e966bd0c6b7b96a0 (diff)
parente3607652b6dd8cba9d1501cbf44b975a7b159cfa (diff)
downloadnixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.tar
nixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.tar.gz
nixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.tar.bz2
nixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.tar.lz
nixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.tar.xz
nixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.tar.zst
nixpkgs-7bff759fac26f1312d08e78bb0f654139b96e798.zip
Merge staging-next into staging
Diffstat (limited to 'lib')
-rw-r--r--lib/generators.nix59
-rw-r--r--lib/options.nix4
-rw-r--r--lib/tests/misc.nix72
-rwxr-xr-xlib/tests/modules.sh29
-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.nix36
11 files changed, 310 insertions, 27 deletions
diff --git a/lib/generators.nix b/lib/generators.nix
index abd237eb7d3..501a23599f4 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 38f4f1329f2..0494a597ab8 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 03eff4ce48b..3a6db53c276 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 943deebe3c0..cfe474d4ded 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 00000000000..085cbd638f1
--- /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 00000000000..972711201a0
--- /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 00000000000..07951891391
--- /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 00000000000..bd846afd3d1
--- /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 00000000000..f84ad01df01
--- /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 00000000000..e57d33ef871
--- /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 ef2c78082f8..aae45366b8f 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";
     };