about summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-xlib/tests/modules.sh3
-rw-r--r--lib/tests/modules/deferred-module.nix54
-rw-r--r--lib/types.nix8
-rw-r--r--nixos/doc/manual/development/option-types.section.md19
-rw-r--r--nixos/doc/manual/from_md/development/option-types.section.xml34
5 files changed, 118 insertions, 0 deletions
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 36af32ca89da0..9b1348f58c9c2 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -194,6 +194,9 @@ checkConfigOutput '^"submodule"$' options.submodule.type.description ./declare-s
 ## Paths should be allowed as values and work as expected
 checkConfigOutput '^true$' config.submodule.enable ./declare-submoduleWith-path.nix
 
+## Deferred module
+checkConfigOutput '"beta"' config.nodes.foo.settingsDict.c ./deferred-module.nix
+
 # Check the file location information is propagated into submodules
 checkConfigOutput the-file.nix config.submodule.internalFiles.0 ./submoduleFiles.nix
 
diff --git a/lib/tests/modules/deferred-module.nix b/lib/tests/modules/deferred-module.nix
new file mode 100644
index 0000000000000..faf459a991fa6
--- /dev/null
+++ b/lib/tests/modules/deferred-module.nix
@@ -0,0 +1,54 @@
+{ lib, ... }:
+let
+  inherit (lib) types mkOption setDefaultModuleLocation;
+  inherit (types) deferredModule lazyAttrsOf submodule str raw;
+in
+{
+  imports = [
+    # generic module, declaring submodules:
+    #   - nodes.<name>
+    #   - default
+    # where all nodes include the default
+    ({ config, ... }: {
+      _file = "generic.nix";
+      options.nodes = mkOption {
+        type = lazyAttrsOf (submodule { imports = config.default; });
+        default = {};
+      };
+      options.default = mkOption {
+        type = deferredModule;
+        default = { };
+        description = ''
+          Module that is included in all nodes.
+        '';
+      };
+    })
+
+    {
+      _file = "default-1.nix";
+      default = { config, ... }: {
+        options.settingsDict = lib.mkOption { type = lazyAttrsOf str; default = {}; };
+      };
+    }
+
+    {
+      _file = "default-a-is-b.nix";
+      default = { config, ... }: {
+        settingsDict.a = config.settingsDict.b;
+      };
+    }
+
+    {
+      _file = "nodes-foo.nix";
+      nodes.foo.settingsDict.b = "beta";
+    }
+
+    {
+      _file = "nodes-foo-c-is-a.nix";
+      nodes.foo = { config, ... }: {
+        settingsDict.c = config.settingsDict.a;
+      };
+    }
+
+  ];
+}
diff --git a/lib/types.nix b/lib/types.nix
index caaa6dccc6d41..22a3292644572 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -539,6 +539,14 @@ rec {
       modules = toList modules;
     };
 
+    # A module to be imported in some other part of the configuration.
+    deferredModule = mkOptionType {
+      name = "deferredModule";
+      description = "module";
+      check = t: isAttrs t || isFunction t;
+      merge = loc: defs: map (def: lib.setDefaultModuleLocation "${showOption loc} from ${def.file}" def.value) defs;
+    };
+
     # The type of a type!
     optionType = mkOptionType {
       name = "optionType";
diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md
index d32d4fc50ad79..0241aae1dc896 100644
--- a/nixos/doc/manual/development/option-types.section.md
+++ b/nixos/doc/manual/development/option-types.section.md
@@ -220,6 +220,25 @@ Value types are types that take a value parameter.
         requires using a function:
         `the-submodule = { ... }: { options = { ... }; }`.
 
+`types.deferredModule`
+
+:   Whereas `submodule` represents an option tree, `deferredModule` represents
+    a module value, such as a module file or a configuration.
+
+    It can be set multiple times.
+
+    Module authors can use its value, which is always a list of module values,
+    in `imports` or in `submoduleWith`'s `modules` parameter.
+    Note that `imports` must be evaluated before the module fixpoint. Because
+    of this, deferred modules can only be imported into "other" fixpoints, such
+    as submodules.
+
+    One use case for this type is the type of a "default" module that allow the
+    user to affect all submodules in an `attrsOf submodule` at once. This is
+    more convenient and discoverable than expecting the module user to
+    type-merge with the `attrsOf submodule` option. NixOps uses this type in
+    `network.defaults`.
+
 ## Composed Types {#sec-option-types-composed}
 
 Composed types are types that take a type as parameter. `listOf
diff --git a/nixos/doc/manual/from_md/development/option-types.section.xml b/nixos/doc/manual/from_md/development/option-types.section.xml
index c67e183581c2c..820646be671f4 100644
--- a/nixos/doc/manual/from_md/development/option-types.section.xml
+++ b/nixos/doc/manual/from_md/development/option-types.section.xml
@@ -427,6 +427,40 @@
           </itemizedlist>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          <literal>types.deferredModule</literal>
+        </term>
+        <listitem>
+          <para>
+            Whereas <literal>submodule</literal> represents an option
+            tree, <literal>deferredModule</literal> represents a module
+            value, such as a module file or a configuration.
+          </para>
+          <para>
+            It can be set multiple times.
+          </para>
+          <para>
+            Module authors can use its value, which is always a list of
+            module values, in <literal>imports</literal> or in
+            <literal>submoduleWith</literal>’s
+            <literal>modules</literal> parameter. Note that
+            <literal>imports</literal> must be evaluated before the
+            module fixpoint. Because of this, deferred modules can only
+            be imported into <quote>other</quote> fixpoints, such as
+            submodules.
+          </para>
+          <para>
+            One use case for this type is the type of a
+            <quote>default</quote> module that allow the user to affect
+            all submodules in an <literal>attrsOf submodule</literal> at
+            once. This is more convenient and discoverable than
+            expecting the module user to type-merge with the
+            <literal>attrsOf submodule</literal> option. NixOps uses
+            this type in <literal>network.defaults</literal>.
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </section>
   <section xml:id="sec-option-types-composed">