about summary refs log tree commit diff
path: root/nixpkgs/lib
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2022-03-15 10:36:38 +0000
committerAlyssa Ross <hi@alyssa.is>2022-03-16 11:37:19 +0000
commitd435710923ac6e6f9fc155534800745004f2ce93 (patch)
tree386f9401476f96bdc6ec25173a090198942b5d5b /nixpkgs/lib
parentc725f0011e91ae49d351b981690eb66b862b6104 (diff)
parent3239fd2b8f728106491154b44625662e10259af2 (diff)
downloadnixlib-d435710923ac6e6f9fc155534800745004f2ce93.tar
nixlib-d435710923ac6e6f9fc155534800745004f2ce93.tar.gz
nixlib-d435710923ac6e6f9fc155534800745004f2ce93.tar.bz2
nixlib-d435710923ac6e6f9fc155534800745004f2ce93.tar.lz
nixlib-d435710923ac6e6f9fc155534800745004f2ce93.tar.xz
nixlib-d435710923ac6e6f9fc155534800745004f2ce93.tar.zst
nixlib-d435710923ac6e6f9fc155534800745004f2ce93.zip
Merge commit '3239fd2b8f728106491154b44625662e10259af2'
Conflicts:
	nixpkgs/pkgs/applications/window-managers/sway/default.nix
Diffstat (limited to 'nixpkgs/lib')
-rw-r--r--nixpkgs/lib/attrsets.nix2
-rw-r--r--nixpkgs/lib/default.nix2
-rw-r--r--nixpkgs/lib/modules.nix15
-rw-r--r--nixpkgs/lib/systems/parse.nix4
-rwxr-xr-xnixpkgs/lib/tests/modules.sh30
-rw-r--r--nixpkgs/lib/tests/modules/adhoc-freeformType-survives-type-merge.nix14
-rw-r--r--nixpkgs/lib/tests/modules/emptyValues.nix36
-rw-r--r--nixpkgs/lib/tests/modules/freeform-submodules.nix22
-rw-r--r--nixpkgs/lib/tests/modules/optionTypeFile.nix28
-rw-r--r--nixpkgs/lib/tests/modules/optionTypeMerging.nix27
-rw-r--r--nixpkgs/lib/tests/modules/raw.nix30
-rw-r--r--nixpkgs/lib/types.nix58
12 files changed, 250 insertions, 18 deletions
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix
index a88947b45858..c0d3ede73d0e 100644
--- a/nixpkgs/lib/attrsets.nix
+++ b/nixpkgs/lib/attrsets.nix
@@ -327,7 +327,7 @@ rec {
        isDerivation "foobar"
        => false
   */
-  isDerivation = x: isAttrs x && x ? type && x.type == "derivation";
+  isDerivation = x: x.type or null == "derivation";
 
   /* Converts a store path to a fake derivation. */
   toDerivation = path:
diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix
index 3e43733ad20f..3fead03a4636 100644
--- a/nixpkgs/lib/default.nix
+++ b/nixpkgs/lib/default.nix
@@ -126,7 +126,7 @@ let
       getValues getFiles
       optionAttrSetToDocList optionAttrSetToDocList'
       scrubOptionValue literalExpression literalExample literalDocBook
-      showOption showFiles unknownModule mkOption;
+      showOption showFiles unknownModule mkOption mkPackageOption;
     inherit (self.types) isType setType defaultTypeMerge defaultFunctor
       isOptionType mkOptionType;
     inherit (self.asserts)
diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix
index f1125aca0a35..4c4d9f994dae 100644
--- a/nixpkgs/lib/modules.nix
+++ b/nixpkgs/lib/modules.nix
@@ -138,7 +138,7 @@ rec {
             # 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;
+            type = types.lazyAttrsOf types.raw;
             internal = true;
             description = "Arguments passed to each module.";
           };
@@ -151,8 +151,7 @@ rec {
           };
 
           _module.freeformType = mkOption {
-            # Disallow merging for now, but could be implemented nicely with a `types.optionType`
-            type = types.nullOr (types.uniq types.attrs);
+            type = types.nullOr types.optionType;
             internal = true;
             default = null;
             description = ''
@@ -762,13 +761,13 @@ rec {
       options = opt.options or
         (throw "Option `${showOption loc}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
       f = tp:
-        let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
-        in
         if tp.name == "option set" || tp.name == "submodule" then
           throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
-        else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
-        else if optionSetIn "listOf"  then types.listOf  (types.submodule options)
-        else if optionSetIn "nullOr"  then types.nullOr  (types.submodule options)
+        else if (tp.functor.wrapped.name or null) == "optionSet" then
+          if tp.name == "attrsOf" then types.attrsOf (types.submodule options)
+          else if tp.name == "listOf" then types.listOf  (types.submodule options)
+          else if tp.name == "nullOr" then types.nullOr  (types.submodule options)
+          else tp
         else tp;
     in
       if opt.type.getSubModules or null == null
diff --git a/nixpkgs/lib/systems/parse.nix b/nixpkgs/lib/systems/parse.nix
index 8a88d8cfbe87..f0e87c30e473 100644
--- a/nixpkgs/lib/systems/parse.nix
+++ b/nixpkgs/lib/systems/parse.nix
@@ -364,8 +364,8 @@ rec {
     musleabihf   = { float = "hard"; };
     musl         = {};
 
-    uclibceabihf = { float = "soft"; };
-    uclibceabi   = { float = "hard"; };
+    uclibceabi   = { float = "soft"; };
+    uclibceabihf = { float = "hard"; };
     uclibc       = {};
 
     unknown = {};
diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh
index 590937da5b8f..350fe85e7487 100755
--- a/nixpkgs/lib/tests/modules.sh
+++ b/nixpkgs/lib/tests/modules.sh
@@ -240,6 +240,11 @@ checkConfigOutput '^"24"$' config.foo ./freeform-attrsOf.nix ./freeform-str-dep-
 checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.nix ./freeform-unstr-dep-str.nix
 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
+# submodules in freeformTypes should have their locations annotated
+checkConfigOutput '/freeform-submodules.nix"$' config.fooDeclarations.0 ./freeform-submodules.nix
+# freeformTypes can get merged using `types.type`, including submodules
+checkConfigOutput '^10$' config.free.xxx.foo ./freeform-submodules.nix
+checkConfigOutput '^10$' config.free.yyy.bar ./freeform-submodules.nix
 
 ## types.anything
 # Check that attribute sets are merged recursively
@@ -284,6 +289,31 @@ checkConfigOutput '^"a b"$' config.resultFoo ./declare-variants.nix ./define-var
 checkConfigOutput '^"a y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix
 checkConfigOutput '^"a b c"$' config.resultFooFoo ./declare-variants.nix ./define-variant.nix
 
+## emptyValue's
+checkConfigOutput "[ ]" config.list.a ./emptyValues.nix
+checkConfigOutput "{ }" config.attrs.a ./emptyValues.nix
+checkConfigOutput "null" config.null.a ./emptyValues.nix
+checkConfigOutput "{ }" config.submodule.a ./emptyValues.nix
+# These types don't have empty values
+checkConfigError 'The option .int.a. is used but not defined' config.int.a ./emptyValues.nix
+checkConfigError 'The option .nonEmptyList.a. is used but not defined' config.nonEmptyList.a ./emptyValues.nix
+
+## types.raw
+checkConfigOutput "{ foo = <CODE>; }" config.unprocessedNesting ./raw.nix
+checkConfigOutput "10" config.processedToplevel ./raw.nix
+checkConfigError "The option .multiple. is defined multiple times" config.multiple ./raw.nix
+checkConfigOutput "bar" config.priorities ./raw.nix
+
+# Test that types.optionType merges types correctly
+checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
+checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
+
+# Test that types.optionType correctly annotates option locations
+checkConfigError 'The option .theOption.nested. in .other.nix. is already declared in .optionTypeFile.nix.' config.theOption.nested ./optionTypeFile.nix
+
+# Test that types.optionType leaves types untouched as long as they don't need to be merged
+checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survives-type-merge.nix
+
 cat <<EOF
 ====== module tests ======
 $pass Pass
diff --git a/nixpkgs/lib/tests/modules/adhoc-freeformType-survives-type-merge.nix b/nixpkgs/lib/tests/modules/adhoc-freeformType-survives-type-merge.nix
new file mode 100644
index 000000000000..3cefb543c256
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/adhoc-freeformType-survives-type-merge.nix
@@ -0,0 +1,14 @@
+{ lib, ... }: {
+  options.dummy = lib.mkOption { type = lib.types.anything; default = {}; };
+  freeformType =
+    let
+      a = lib.types.attrsOf (lib.types.submodule { options.bar = lib.mkOption { }; });
+    in
+    # modifying types like this breaks type merging.
+    # This test makes sure that type merging is not performed when only a single declaration exists.
+    # Don't modify types in practice!
+    a // {
+      merge = loc: defs: { freeformItems = a.merge loc defs; };
+    };
+  config.foo.bar = "ok";
+}
diff --git a/nixpkgs/lib/tests/modules/emptyValues.nix b/nixpkgs/lib/tests/modules/emptyValues.nix
new file mode 100644
index 000000000000..77825de3281a
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/emptyValues.nix
@@ -0,0 +1,36 @@
+{ lib, ... }:
+let
+  inherit (lib) types;
+in {
+
+  options = {
+    int = lib.mkOption {
+      type = types.lazyAttrsOf types.int;
+    };
+    list = lib.mkOption {
+      type = types.lazyAttrsOf (types.listOf types.int);
+    };
+    nonEmptyList = lib.mkOption {
+      type = types.lazyAttrsOf (types.nonEmptyListOf types.int);
+    };
+    attrs = lib.mkOption {
+      type = types.lazyAttrsOf (types.attrsOf types.int);
+    };
+    null = lib.mkOption {
+      type = types.lazyAttrsOf (types.nullOr types.int);
+    };
+    submodule = lib.mkOption {
+      type = types.lazyAttrsOf (types.submodule {});
+    };
+  };
+
+  config = {
+    int.a = lib.mkIf false null;
+    list.a = lib.mkIf false null;
+    nonEmptyList.a = lib.mkIf false null;
+    attrs.a = lib.mkIf false null;
+    null.a = lib.mkIf false null;
+    submodule.a = lib.mkIf false null;
+  };
+
+}
diff --git a/nixpkgs/lib/tests/modules/freeform-submodules.nix b/nixpkgs/lib/tests/modules/freeform-submodules.nix
new file mode 100644
index 000000000000..3910435a7b5c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/freeform-submodules.nix
@@ -0,0 +1,22 @@
+{ lib, options, ... }: with lib.types; {
+
+  options.fooDeclarations = lib.mkOption {
+    default = (options.free.type.getSubOptions [])._freeformOptions.foo.declarations;
+  };
+
+  options.free = lib.mkOption {
+    type = submodule {
+      config._module.freeformType = lib.mkMerge [
+        (attrsOf (submodule {
+          options.foo = lib.mkOption {};
+        }))
+        (attrsOf (submodule {
+          options.bar = lib.mkOption {};
+        }))
+      ];
+    };
+  };
+
+  config.free.xxx.foo = 10;
+  config.free.yyy.bar = 10;
+}
diff --git a/nixpkgs/lib/tests/modules/optionTypeFile.nix b/nixpkgs/lib/tests/modules/optionTypeFile.nix
new file mode 100644
index 000000000000..6015d59a72c9
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/optionTypeFile.nix
@@ -0,0 +1,28 @@
+{ config, lib, ... }: {
+
+  _file = "optionTypeFile.nix";
+
+  options.theType = lib.mkOption {
+    type = lib.types.optionType;
+  };
+
+  options.theOption = lib.mkOption {
+    type = config.theType;
+    default = {};
+  };
+
+  config.theType = lib.mkMerge [
+    (lib.types.submodule {
+      options.nested = lib.mkOption {
+        type = lib.types.int;
+      };
+    })
+    (lib.types.submodule {
+      _file = "other.nix";
+      options.nested = lib.mkOption {
+        type = lib.types.str;
+      };
+    })
+  ];
+
+}
diff --git a/nixpkgs/lib/tests/modules/optionTypeMerging.nix b/nixpkgs/lib/tests/modules/optionTypeMerging.nix
new file mode 100644
index 000000000000..74a620c4620c
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/optionTypeMerging.nix
@@ -0,0 +1,27 @@
+{ config, lib, ... }: {
+
+  options.theType = lib.mkOption {
+    type = lib.types.optionType;
+  };
+
+  options.theOption = lib.mkOption {
+    type = config.theType;
+  };
+
+  config.theType = lib.mkMerge [
+    (lib.types.submodule {
+      options.int = lib.mkOption {
+        type = lib.types.int;
+        default = 10;
+      };
+    })
+    (lib.types.submodule {
+      options.str = lib.mkOption {
+        type = lib.types.str;
+      };
+    })
+  ];
+
+  config.theOption.str = "hello";
+
+}
diff --git a/nixpkgs/lib/tests/modules/raw.nix b/nixpkgs/lib/tests/modules/raw.nix
new file mode 100644
index 000000000000..418e671ed076
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/raw.nix
@@ -0,0 +1,30 @@
+{ lib, ... }: {
+
+  options = {
+    processedToplevel = lib.mkOption {
+      type = lib.types.raw;
+    };
+    unprocessedNesting = lib.mkOption {
+      type = lib.types.raw;
+    };
+    multiple = lib.mkOption {
+      type = lib.types.raw;
+    };
+    priorities = lib.mkOption {
+      type = lib.types.raw;
+    };
+  };
+
+  config = {
+    processedToplevel = lib.mkIf true 10;
+    unprocessedNesting.foo = throw "foo";
+    multiple = lib.mkMerge [
+      "foo"
+      "foo"
+    ];
+    priorities = lib.mkMerge [
+      "foo"
+      (lib.mkForce "bar")
+    ];
+  };
+}
diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix
index f2f9b2bca985..3078615f5ddc 100644
--- a/nixpkgs/lib/types.nix
+++ b/nixpkgs/lib/types.nix
@@ -61,7 +61,11 @@ let
     boolToString
     ;
 
-  inherit (lib.modules) mergeDefinitions;
+  inherit (lib.modules)
+    mergeDefinitions
+    fixupOptionType
+    mergeOptionDecls
+    ;
   outer_types =
 rec {
   isType = type: x: (x._type or "") == type;
@@ -162,6 +166,13 @@ rec {
   # nixos/doc/manual/development/option-types.xml!
   types = rec {
 
+    raw = mkOptionType rec {
+      name = "raw";
+      description = "raw value";
+      check = value: true;
+      merge = mergeOneOption;
+    };
+
     anything = mkOptionType {
       name = "anything";
       description = "anything";
@@ -357,13 +368,21 @@ rec {
       emptyValue = { value = {}; };
     };
 
-    # derivation is a reserved keyword.
+    # A package is a top-level store path (/nix/store/hash-name). This includes:
+    # - derivations
+    # - more generally, attribute sets with an `outPath` or `__toString` attribute
+    #   pointing to a store path, e.g. flake inputs
+    # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
+    # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
+    #   ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
     package = mkOptionType {
       name = "package";
       check = x: isDerivation x || isStorePath x;
       merge = loc: defs:
         let res = mergeOneOption loc defs;
-        in if isDerivation res then res else toDerivation res;
+        in if builtins.isPath res || (builtins.isString res && ! builtins.hasContext res)
+          then toDerivation res
+          else res;
     };
 
     shellPackage = package // {
@@ -390,7 +409,7 @@ rec {
             ).optionalValue
           ) def.value
         ) defs)));
-      emptyValue = { value = {}; };
+      emptyValue = { value = []; };
       getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
       getSubModules = elemType.getSubModules;
       substSubModules = m: listOf (elemType.substSubModules m);
@@ -402,7 +421,7 @@ rec {
       let list = addCheck (types.listOf elemType) (l: l != []);
       in list // {
         description = "non-empty " + list.description;
-        # Note: emptyValue is left as is, because another module may define an element.
+        emptyValue = { }; # no .value attr, meaning unset
       };
 
     attrsOf = elemType: mkOptionType rec {
@@ -503,7 +522,7 @@ rec {
 
     functionTo = elemType: mkOptionType {
       name = "functionTo";
-      description = "function that evaluates to a(n) ${elemType.name}";
+      description = "function that evaluates to a(n) ${elemType.description}";
       check = isFunction;
       merge = loc: defs:
         fnArgs: (mergeDefinitions (loc ++ [ "[function body]" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
@@ -518,6 +537,33 @@ rec {
       modules = toList modules;
     };
 
+    # The type of a type!
+    optionType = mkOptionType {
+      name = "optionType";
+      description = "optionType";
+      check = value: value._type or null == "option-type";
+      merge = loc: defs:
+        if length defs == 1
+        then (head defs).value
+        else let
+          # Prepares the type definitions for mergeOptionDecls, which
+          # annotates submodules types with file locations
+          optionModules = map ({ value, file }:
+            {
+              _file = file;
+              # There's no way to merge types directly from the module system,
+              # but we can cheat a bit by just declaring an option with the type
+              options = lib.mkOption {
+                type = value;
+              };
+            }
+          ) defs;
+          # Merges all the types into a single one, including submodule merging.
+          # This also propagates file information to all submodules
+          mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
+        in mergedOption.type;
+    };
+
     submoduleWith =
       { modules
       , specialArgs ? {}