Merge pull request #163617 from hercules-ci/lib-types-deferredModule
lib.types: Add deferredModule
This commit is contained in:
commit
8f8db59c0e
8 changed files with 175 additions and 0 deletions
|
@ -462,6 +462,7 @@ rec {
|
||||||
config = addFreeformType (addMeta (m.config or {}));
|
config = addFreeformType (addMeta (m.config or {}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
lib.throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module."
|
||||||
{ _file = toString m._file or file;
|
{ _file = toString m._file or file;
|
||||||
key = toString m.key or key;
|
key = toString m.key or key;
|
||||||
disabledModules = m.disabledModules or [];
|
disabledModules = m.disabledModules or [];
|
||||||
|
|
|
@ -194,6 +194,13 @@ checkConfigOutput '^"submodule"$' options.submodule.type.description ./declare-s
|
||||||
## Paths should be allowed as values and work as expected
|
## Paths should be allowed as values and work as expected
|
||||||
checkConfigOutput '^true$' config.submodule.enable ./declare-submoduleWith-path.nix
|
checkConfigOutput '^true$' config.submodule.enable ./declare-submoduleWith-path.nix
|
||||||
|
|
||||||
|
## deferredModule
|
||||||
|
# default module is merged into nodes.foo
|
||||||
|
checkConfigOutput '"beta"' config.nodes.foo.settingsDict.c ./deferred-module.nix
|
||||||
|
# errors from the default module are reported with accurate location
|
||||||
|
checkConfigError 'In `the-file-that-contains-the-bad-config.nix, via option default'\'': "bogus"' config.nodes.foo.bottom ./deferred-module.nix
|
||||||
|
checkConfigError '.*lib/tests/modules/deferred-module-error.nix, via option deferred [(]:anon-1:anon-1:anon-1[)] does not look like a module.' config.result ./deferred-module-error.nix
|
||||||
|
|
||||||
# Check the file location information is propagated into submodules
|
# Check the file location information is propagated into submodules
|
||||||
checkConfigOutput the-file.nix config.submodule.internalFiles.0 ./submoduleFiles.nix
|
checkConfigOutput the-file.nix config.submodule.internalFiles.0 ./submoduleFiles.nix
|
||||||
|
|
||||||
|
|
20
lib/tests/modules/deferred-module-error.nix
Normal file
20
lib/tests/modules/deferred-module-error.nix
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption setDefaultModuleLocation evalModules;
|
||||||
|
inherit (types) deferredModule lazyAttrsOf submodule str raw enum;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
deferred = mkOption {
|
||||||
|
type = deferredModule;
|
||||||
|
};
|
||||||
|
result = mkOption {
|
||||||
|
default = (evalModules { modules = [ config.deferred ]; }).config.result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
deferred = { ... }:
|
||||||
|
# this should be an attrset, so this fails
|
||||||
|
true;
|
||||||
|
};
|
||||||
|
}
|
58
lib/tests/modules/deferred-module.nix
Normal file
58
lib/tests/modules/deferred-module.nix
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption setDefaultModuleLocation;
|
||||||
|
inherit (types) deferredModule lazyAttrsOf submodule str raw enum;
|
||||||
|
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 = {}; };
|
||||||
|
options.bottom = lib.mkOption { type = enum []; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_file = "default-a-is-b.nix";
|
||||||
|
default = ./define-settingsDict-a-is-b.nix;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_file = "nodes-foo.nix";
|
||||||
|
nodes.foo.settingsDict.b = "beta";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_file = "the-file-that-contains-the-bad-config.nix";
|
||||||
|
default.bottom = "bogus";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_file = "nodes-foo-c-is-a.nix";
|
||||||
|
nodes.foo = { config, ... }: {
|
||||||
|
settingsDict.c = config.settingsDict.a;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
3
lib/tests/modules/define-settingsDict-a-is-b.nix
Normal file
3
lib/tests/modules/define-settingsDict-a-is-b.nix
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{ config, ... }: {
|
||||||
|
settingsDict.a = config.settingsDict.b;
|
||||||
|
}
|
|
@ -539,6 +539,36 @@ rec {
|
||||||
modules = toList modules;
|
modules = toList modules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# A module to be imported in some other part of the configuration.
|
||||||
|
deferredModule = deferredModuleWith { };
|
||||||
|
|
||||||
|
# A module to be imported in some other part of the configuration.
|
||||||
|
# `staticModules`' options will be added to the documentation, unlike
|
||||||
|
# options declared via `config`.
|
||||||
|
deferredModuleWith = attrs@{ staticModules ? [] }: mkOptionType {
|
||||||
|
name = "deferredModule";
|
||||||
|
description = "module";
|
||||||
|
check = x: isAttrs x || isFunction x || path.check x;
|
||||||
|
merge = loc: defs: {
|
||||||
|
imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
|
||||||
|
};
|
||||||
|
inherit (submoduleWith { modules = staticModules; })
|
||||||
|
getSubOptions
|
||||||
|
getSubModules;
|
||||||
|
substSubModules = m: deferredModuleWith (attrs // {
|
||||||
|
staticModules = m;
|
||||||
|
});
|
||||||
|
functor = defaultFunctor "deferredModuleWith" // {
|
||||||
|
type = types.deferredModuleWith;
|
||||||
|
payload = {
|
||||||
|
inherit staticModules;
|
||||||
|
};
|
||||||
|
binOp = lhs: rhs: {
|
||||||
|
staticModules = lhs.staticModules ++ rhs.staticModules;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# The type of a type!
|
# The type of a type!
|
||||||
optionType = mkOptionType {
|
optionType = mkOptionType {
|
||||||
name = "optionType";
|
name = "optionType";
|
||||||
|
|
|
@ -220,6 +220,25 @@ Value types are types that take a value parameter.
|
||||||
requires using a function:
|
requires using a function:
|
||||||
`the-submodule = { ... }: { options = { ... }; }`.
|
`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 in `imports`, in `submoduleWith`'s `modules`
|
||||||
|
or in `evalModules`' `modules` parameter, among other places.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Composed Types {#sec-option-types-composed}
|
## Composed Types {#sec-option-types-composed}
|
||||||
|
|
||||||
Composed types are types that take a type as parameter. `listOf
|
Composed types are types that take a type as parameter. `listOf
|
||||||
|
|
|
@ -427,6 +427,43 @@
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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 in
|
||||||
|
<literal>imports</literal>, in
|
||||||
|
<literal>submoduleWith</literal><quote>s
|
||||||
|
<literal>modules</literal> or in
|
||||||
|
<literal>evalModules</literal></quote>
|
||||||
|
<literal>modules</literal> parameter, among other places.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
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.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</section>
|
</section>
|
||||||
<section xml:id="sec-option-types-composed">
|
<section xml:id="sec-option-types-composed">
|
||||||
|
|
Loading…
Reference in a new issue