summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2021-11-01 12:01:00 +0000
committerGitHub <noreply@github.com>2021-11-01 12:01:00 +0000
commitcc41bb585179fca8d8443e0ffb58c3a24458564d (patch)
tree51d1967cfd81307013894b4126ba8fdd5cee3946 /lib
parent2b9973012c1d1f7f3e3457f2569f0517a97b1b81 (diff)
parent4559160c4f4eb55f9eee0cb6015e2a0f3fa80462 (diff)
downloadnixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.tar
nixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.tar.gz
nixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.tar.bz2
nixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.tar.lz
nixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.tar.xz
nixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.tar.zst
nixpkgs-cc41bb585179fca8d8443e0ffb58c3a24458564d.zip
Merge master into staging-next
Diffstat (limited to 'lib')
-rw-r--r--lib/modules.nix62
-rw-r--r--lib/options.nix10
-rwxr-xr-xlib/tests/modules.sh7
-rw-r--r--lib/tests/modules/declare-submodule-via-evalModules.nix28
-rw-r--r--lib/types.nix56
5 files changed, 123 insertions, 40 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
index 46ae3f13631..d9b4000e56b 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -52,15 +52,39 @@ in
 
 rec {
 
-  /* Evaluate a set of modules.  The result is a set of two
-     attributes: ‘options’: the nested set of all option declarations,
-     and ‘config’: the nested set of all option values.
+  /*
+    Evaluate a set of modules.  The result is a set with the attributes:
+
+      ‘options’: The nested set of all option declarations,
+
+      ‘config’: The nested set of all option values.
+
+      ‘type’: A module system type representing the module set as a submodule,
+            to be extended by configuration from the containing module set.
+
+      ‘extendModules’: A function similar to ‘evalModules’ but building on top
+            of the module set. Its arguments, ‘modules’ and ‘specialArgs’ are
+            added to the existing values.
+
+            Using ‘extendModules’ a few times has no performance impact as long
+            as you only reference the final ‘options’ and ‘config’.
+            If you do reference multiple ‘config’ (or ‘options’) from before and
+            after ‘extendModules’, performance is the same as with multiple
+            ‘evalModules’ invocations, because the new modules' ability to
+            override existing configuration fundamentally requires a new
+            fixpoint to be constructed.
+
+      ‘_module’: A portion of the configuration tree which is elided from
+            ‘config’. It contains some values that are mostly internal to the
+            module system implementation.
+
      !!! Please think twice before adding to this argument list! The more
      that is specified here instead of in the modules themselves the harder
      it is to transparently move a set of modules to be a submodule of another
      config (as the proper arguments need to be replicated at each call to
      evalModules) and the less declarative the module set is. */
-  evalModules = { modules
+  evalModules = evalModulesArgs@
+                { modules
                 , prefix ? []
                 , # This should only be used for special arguments that need to be evaluated
                   # when resolving module structure (like in imports). For everything else,
@@ -120,7 +144,9 @@ rec {
         };
 
         config = {
-          _module.args = args;
+          _module.args = {
+            inherit extendModules;
+          } // args;
         };
       };
 
@@ -183,10 +209,28 @@ rec {
             else throw baseMsg
         else null;
 
-      result = builtins.seq checkUnmatched {
-        inherit options;
-        config = removeAttrs config [ "_module" ];
-        inherit (config) _module;
+      checked = builtins.seq checkUnmatched;
+
+      extendModules = extendArgs@{
+        modules ? [],
+        specialArgs ? {},
+        prefix ? [],
+        }:
+          evalModules (evalModulesArgs // {
+            modules = evalModulesArgs.modules ++ modules;
+            specialArgs = evalModulesArgs.specialArgs or {} // specialArgs;
+            prefix = extendArgs.prefix or evalModulesArgs.prefix;
+          });
+
+      type = lib.types.submoduleWith {
+        inherit modules specialArgs;
+      };
+
+      result = {
+        options = checked options;
+        config = checked (removeAttrs config [ "_module" ]);
+        _module = checked (config._module);
+        inherit extendModules type;
       };
     in result;
 
diff --git a/lib/options.nix b/lib/options.nix
index b3164181312..5d52f065af0 100644
--- a/lib/options.nix
+++ b/lib/options.nix
@@ -74,7 +74,7 @@ rec {
     apply ? null,
     # Whether the option is for NixOS developers only.
     internal ? null,
-    # Whether the option shows up in the manual.
+    # Whether the option shows up in the manual. Default: true. Use false to hide the option and any sub-options from submodules. Use "shallow" to hide only sub-options.
     visible ? null,
     # Whether the option can be set only once
     readOnly ? null,
@@ -180,7 +180,10 @@ rec {
           description = opt.description or (lib.warn "Option `${name}' has no description." "This option has no description.");
           declarations = filter (x: x != unknownModule) opt.declarations;
           internal = opt.internal or false;
-          visible = opt.visible or true;
+          visible =
+            if (opt?visible && opt.visible == "shallow")
+            then true
+            else opt.visible or true;
           readOnly = opt.readOnly or false;
           type = opt.type.description or null;
         }
@@ -192,8 +195,9 @@ rec {
         subOptions =
           let ss = opt.type.getSubOptions opt.loc;
           in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
+        subOptionsVisible = docOption.visible && opt.visible or null != "shallow";
       in
-        [ docOption ] ++ optionals docOption.visible subOptions) (collect isOption options);
+        [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options);
 
 
   /* This function recursively removes all derivation attributes from
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index b51db91f6b0..49fc8bcbafc 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -179,6 +179,13 @@ checkConfigOutput "true" config.submodule.outer ./declare-submoduleWith-modules.
 # which evaluates all the modules defined by the type)
 checkConfigOutput "submodule" options.submodule.type.description ./declare-submoduleWith-modules.nix
 
+## submodules can be declared using (evalModules {...}).type
+checkConfigOutput "true" config.submodule.inner ./declare-submodule-via-evalModules.nix
+checkConfigOutput "true" config.submodule.outer ./declare-submodule-via-evalModules.nix
+# Should also be able to evaluate the type name (which evaluates freeformType,
+# which evaluates all the modules defined by the type)
+checkConfigOutput "submodule" options.submodule.type.description ./declare-submodule-via-evalModules.nix
+
 ## Paths should be allowed as values and work as expected
 checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
 
diff --git a/lib/tests/modules/declare-submodule-via-evalModules.nix b/lib/tests/modules/declare-submodule-via-evalModules.nix
new file mode 100644
index 00000000000..2841c64a073
--- /dev/null
+++ b/lib/tests/modules/declare-submodule-via-evalModules.nix
@@ -0,0 +1,28 @@
+{ lib, ... }: {
+  options.submodule = lib.mkOption {
+    inherit (lib.evalModules {
+      modules = [
+        {
+          options.inner = lib.mkOption {
+            type = lib.types.bool;
+            default = false;
+          };
+        }
+      ];
+    }) type;
+    default = {};
+  };
+
+  config.submodule = lib.mkMerge [
+    ({ lib, ... }: {
+      options.outer = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+      };
+    })
+    {
+      inner = true;
+      outer = true;
+    }
+  ];
+}
diff --git a/lib/types.nix b/lib/types.nix
index c2532065d7e..244cbb6b535 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -505,17 +505,36 @@ rec {
           then setFunctionArgs (args: unify (value args)) (functionArgs value)
           else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
 
-        allModules = defs: modules ++ imap1 (n: { value, file }:
+        allModules = defs: imap1 (n: { value, file }:
           if isAttrs value || isFunction value then
             # Annotate the value with the location of its definition for better error messages
             coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
           else value
         ) defs;
 
-        freeformType = (evalModules {
-          inherit modules specialArgs;
-          args.name = "‹name›";
-        })._module.freeformType;
+        base = evalModules {
+          inherit specialArgs;
+          modules = [{
+            # This is a work-around for the fact that some sub-modules,
+            # such as the one included in an attribute set, expects an "args"
+            # attribute to be given to the sub-module. As the option
+            # evaluation does not have any specific attribute name yet, we
+            # provide a default for the documentation and the freeform type.
+            #
+            # This is necessary as some option declaration might use the
+            # "name" attribute given as argument of the submodule and use it
+            # as the default of option declarations.
+            #
+            # We use lookalike unicode single angle quotation marks because
+            # of the docbook transformation the options receive. In all uses
+            # &gt; and &lt; wouldn't be encoded correctly so the encoded values
+            # would be used, and use of `<` and `>` would break the XML document.
+            # It shouldn't cause an issue since this is cosmetic for the manual.
+            _module.args.name = lib.mkOptionDefault "‹name›";
+          }] ++ modules;
+        };
+
+        freeformType = base._module.freeformType;
 
       in
       mkOptionType rec {
@@ -523,32 +542,13 @@ rec {
         description = freeformType.description or name;
         check = x: isAttrs x || isFunction x || path.check x;
         merge = loc: defs:
-          (evalModules {
-            modules = allModules defs;
-            inherit specialArgs;
-            args.name = last loc;
+          (base.extendModules {
+            modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
             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,
-            # such as the one included in an attribute set, expects a "args"
-            # attribute to be given to the sub-module. As the option
-            # evaluation does not have any specific attribute name, we
-            # provide a default one for the documentation.
-            #
-            # This is mandatory as some option declaration might use the
-            # "name" attribute given as argument of the submodule and use it
-            # as the default of option declarations.
-            #
-            # Using lookalike unicode single angle quotation marks because
-            # of the docbook transformation the options receive. In all uses
-            # &gt; and &lt; wouldn't be encoded correctly so the encoded values
-            # would be used, and use of `<` and `>` would break the XML document.
-            # It shouldn't cause an issue since this is cosmetic for the manual.
-            args.name = "‹name›";
-          }).options // optionalAttrs (freeformType != null) {
+        getSubOptions = prefix: (base.extendModules
+          { inherit prefix; }).options // optionalAttrs (freeformType != null) {
             # Expose the sub options of the freeform type. Note that the option
             # discovery doesn't care about the attribute name used here, so this
             # is just to avoid conflicts with potential options from the submodule