summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorFrederik Rietdijk <fridh@fridh.nl>2020-01-10 18:32:07 +0100
committerFrederik Rietdijk <fridh@fridh.nl>2020-01-10 18:32:07 +0100
commit348eaa280bdc649211dc34a2a79ab3c21db05532 (patch)
treec70787b060c878d1cfc487cdb7812db7abb7260e /lib
parent5ad16cb27fd81cb67db4da80283e147bea4e1c25 (diff)
parente2e90a0e6422b143d6d6a9539280eded7306126d (diff)
downloadnixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar
nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.gz
nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.bz2
nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.lz
nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.xz
nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.tar.zst
nixpkgs-348eaa280bdc649211dc34a2a79ab3c21db05532.zip
Merge master into staging-next
Diffstat (limited to 'lib')
-rw-r--r--lib/modules.nix33
-rwxr-xr-xlib/tests/modules.sh9
-rw-r--r--lib/tests/modules/attrsOf-conditional-check.nix7
-rw-r--r--lib/tests/modules/attrsOf-lazy-check.nix7
-rw-r--r--lib/tests/modules/declare-attrsOf.nix6
-rw-r--r--lib/tests/modules/declare-lazyAttrsOf.nix6
-rw-r--r--lib/tests/modules/import-from-store.nix12
-rw-r--r--lib/types.nix44
8 files changed, 99 insertions, 25 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
index 559697b3d57..e2315290ff0 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -41,7 +41,13 @@ rec {
 
         options = {
           _module.args = mkOption {
-            type = types.attrsOf types.unspecified;
+            # Because things like `mkIf` are entirely useless for
+            # `_module.args` (because there's no way modules can check which
+            # arguments were passed), we'll use `lazyAttrsOf` which drops
+            # support for that, in turn it's lazy in its values. This means e.g.
+            # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
+            # start a download when `pkgs` wasn't evaluated.
+            type = types.lazyAttrsOf types.unspecified;
             internal = true;
             description = "Arguments passed to each module.";
           };
@@ -365,16 +371,9 @@ rec {
         else
           mergeDefinitions loc opt.type defs';
 
-
-      # The value with a check that it is defined
-      valueDefined = if res.isDefined then res.mergedValue else
-        # (nixos-option detects this specific error message and gives it special
-        # handling.  If changed here, please change it there too.)
-        throw "The option `${showOption loc}' is used but not defined.";
-
       # Apply the 'apply' function to the merged value. This allows options to
       # yield a value computed from the definitions
-      value = if opt ? apply then opt.apply valueDefined else valueDefined;
+      value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
 
     in opt //
       { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
@@ -408,11 +407,17 @@ rec {
       };
     defsFinal = defsFinal'.values;
 
-    # Type-check the remaining definitions, and merge them.
-    mergedValue = foldl' (res: def:
-      if type.check def.value then res
-      else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.")
-      (type.merge loc defsFinal) defsFinal;
+    # Type-check the remaining definitions, and merge them. Or throw if no definitions.
+    mergedValue =
+      if isDefined then
+        foldl' (res: def:
+          if type.check def.value then res
+          else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'."
+        ) (type.merge loc defsFinal) defsFinal
+      else
+        # (nixos-option detects this specific error message and gives it special
+        # handling.  If changed here, please change it there too.)
+        throw "The option `${showOption loc}' is used but not defined.";
 
     isDefined = defsFinal != [];
 
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 79d90670fb5..c8340ff7f15 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -186,6 +186,15 @@ checkConfigError 'The option .* defined in .* does not exist' config.enable ./di
 # Check that imports can depend on derivations
 checkConfigOutput "true" config.enable ./import-from-store.nix
 
+# Check attrsOf and lazyAttrsOf. Only lazyAttrsOf should be lazy, and only
+# attrsOf should work with conditional definitions
+# In addition, lazyAttrsOf should honor an options emptyValue
+checkConfigError "is not lazy" config.isLazy ./declare-attrsOf.nix ./attrsOf-lazy-check.nix
+checkConfigOutput "true" config.isLazy ./declare-lazyAttrsOf.nix ./attrsOf-lazy-check.nix
+checkConfigOutput "true" config.conditionalWorks ./declare-attrsOf.nix ./attrsOf-conditional-check.nix
+checkConfigOutput "false" config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
+checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
+
 cat <<EOF
 ====== module tests ======
 $pass Pass
diff --git a/lib/tests/modules/attrsOf-conditional-check.nix b/lib/tests/modules/attrsOf-conditional-check.nix
new file mode 100644
index 00000000000..0f00ebca155
--- /dev/null
+++ b/lib/tests/modules/attrsOf-conditional-check.nix
@@ -0,0 +1,7 @@
+{ lib, config, ... }: {
+  options.conditionalWorks = lib.mkOption {
+    default = ! config.value ? foo;
+  };
+
+  config.value.foo = lib.mkIf false "should not be defined";
+}
diff --git a/lib/tests/modules/attrsOf-lazy-check.nix b/lib/tests/modules/attrsOf-lazy-check.nix
new file mode 100644
index 00000000000..ec5b418b15a
--- /dev/null
+++ b/lib/tests/modules/attrsOf-lazy-check.nix
@@ -0,0 +1,7 @@
+{ lib, config, ... }: {
+  options.isLazy = lib.mkOption {
+    default = ! config.value ? foo;
+  };
+
+  config.value.bar = throw "is not lazy";
+}
diff --git a/lib/tests/modules/declare-attrsOf.nix b/lib/tests/modules/declare-attrsOf.nix
new file mode 100644
index 00000000000..b3999de7e5f
--- /dev/null
+++ b/lib/tests/modules/declare-attrsOf.nix
@@ -0,0 +1,6 @@
+{ lib, ... }: {
+  options.value = lib.mkOption {
+    type = lib.types.attrsOf lib.types.str;
+    default = {};
+  };
+}
diff --git a/lib/tests/modules/declare-lazyAttrsOf.nix b/lib/tests/modules/declare-lazyAttrsOf.nix
new file mode 100644
index 00000000000..1d9fec25f90
--- /dev/null
+++ b/lib/tests/modules/declare-lazyAttrsOf.nix
@@ -0,0 +1,6 @@
+{ lib, ... }: {
+  options.value = lib.mkOption {
+    type = lib.types.lazyAttrsOf (lib.types.str // { emptyValue.value = "empty"; });
+    default = {};
+  };
+}
diff --git a/lib/tests/modules/import-from-store.nix b/lib/tests/modules/import-from-store.nix
index 64e7ec2e388..f5af22432ce 100644
--- a/lib/tests/modules/import-from-store.nix
+++ b/lib/tests/modules/import-from-store.nix
@@ -1,17 +1,11 @@
 { lib, ... }:
-let
-  drv = derivation {
-    name = "derivation";
-    system = builtins.currentSystem;
-    builder = "/bin/sh";
-    args = [ "-c" "echo {} > $out" ];
-  };
-in {
+{
 
   imports = [
-    "${drv}"
+    "${builtins.toFile "drv" "{}"}"
     ./declare-enable.nix
     ./define-enable.nix
   ];
 
 }
+
diff --git a/lib/types.nix b/lib/types.nix
index 4872a676657..e86f6d36476 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -65,6 +65,11 @@ rec {
       # definition values and locations (e.g. [ { file = "/foo.nix";
       # value = 1; } { file = "/bar.nix"; value = 2 } ]).
       merge ? mergeDefaultOption
+    , # Whether this type has a value representing nothingness. If it does,
+      # this should be a value of the form { value = <the nothing value>; }
+      # If it doesn't, this should be {}
+      # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
+      emptyValue ? {}
     , # Return a flat list of sub-options.  Used to generate
       # documentation.
       getSubOptions ? prefix: {}
@@ -88,7 +93,7 @@ rec {
       functor ? defaultFunctor name
     }:
     { _type = "option-type";
-      inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor;
+      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor;
       description = if description == null then name else description;
     };
 
@@ -225,6 +230,7 @@ rec {
       description = "attribute set";
       check = isAttrs;
       merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
+      emptyValue = { value = {}; };
     };
 
     # derivation is a reserved keyword.
@@ -265,6 +271,7 @@ rec {
             ) def.value
           else
             throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
+      emptyValue = { value = {}; };
       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: listOf (elemType.substSubModules m);
@@ -273,7 +280,10 @@ rec {
 
     nonEmptyListOf = elemType:
       let list = addCheck (types.listOf elemType) (l: l != []);
-      in list // { description = "non-empty " + list.description; };
+      in list // {
+        description = "non-empty " + list.description;
+        # Note: emptyValue is left as is, because another module may define an element.
+      };
 
     attrsOf = elemType: mkOptionType rec {
       name = "attrsOf";
@@ -285,12 +295,37 @@ rec {
           )
           # Push down position info.
           (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
+      emptyValue = { value = {}; };
       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: attrsOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
     };
 
+    # A version of attrsOf that's lazy in its values at the expense of
+    # conditional definitions not working properly. E.g. defining a value with
+    # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
+    # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
+    # error that it's not defined. Use only if conditional definitions don't make sense.
+    lazyAttrsOf = elemType: mkOptionType rec {
+      name = "lazyAttrsOf";
+      description = "lazy attribute set of ${elemType.description}s";
+      check = isAttrs;
+      merge = loc: defs:
+        zipAttrsWith (name: defs:
+          let merged = mergeDefinitions (loc ++ [name]) elemType defs;
+          # mergedValue will trigger an appropriate error when accessed
+          in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
+        )
+        # Push down position info.
+        (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
+      emptyValue = { value = {}; };
+      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
+      getSubModules = elemType.getSubModules;
+      substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
+      functor = (defaultFunctor name) // { wrapped = elemType; };
+    };
+
     # List or attribute set of ...
     loaOf = elemType:
       let
@@ -339,6 +374,7 @@ rec {
         description = "list or attribute set of ${elemType.description}s";
         check = x: isList x || isAttrs x;
         merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs);
+        emptyValue = { value = {}; };
         getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
         getSubModules = elemType.getSubModules;
         substSubModules = m: loaOf (elemType.substSubModules m);
@@ -350,6 +386,7 @@ rec {
       name = "uniq";
       inherit (elemType) description check;
       merge = mergeOneOption;
+      emptyValue = elemType.emptyValue;
       getSubOptions = elemType.getSubOptions;
       getSubModules = elemType.getSubModules;
       substSubModules = m: uniq (elemType.substSubModules m);
@@ -367,6 +404,7 @@ rec {
         else if nrNulls != 0 then
           throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
         else elemType.merge loc defs;
+      emptyValue = { value = null; };
       getSubOptions = elemType.getSubOptions;
       getSubModules = elemType.getSubModules;
       substSubModules = m: nullOr (elemType.substSubModules m);
@@ -407,6 +445,7 @@ rec {
             args.name = last loc;
             prefix = loc;
           }).config;
+        emptyValue = { value = {}; };
         getSubOptions = prefix: (evalModules
           { inherit modules prefix specialArgs;
             # This is a work-around due to the fact that some sub-modules,
@@ -515,6 +554,7 @@ rec {
               if finalType.check val then val
               else coerceFunc val;
           in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
+        emptyValue = finalType.emptyValue;
         getSubOptions = finalType.getSubOptions;
         getSubModules = finalType.getSubModules;
         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);