diff options
-rwxr-xr-x | lib/tests/modules.sh | 3 | ||||
-rw-r--r-- | lib/tests/modules/deferred-module.nix | 54 | ||||
-rw-r--r-- | lib/types.nix | 8 | ||||
-rw-r--r-- | nixos/doc/manual/development/option-types.section.md | 19 | ||||
-rw-r--r-- | nixos/doc/manual/from_md/development/option-types.section.xml | 34 |
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"> |