lib.evalModules: Add extendModules and type to result

Allows the simultaneous construction of top-level invocations and
submodule types.

This helps structure configuration systems integration code.

+22 -5
lib/modules.nix
···
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,
···
else throw baseMsg
else null;
-
result = builtins.seq checkUnmatched {
-
inherit options;
-
config = removeAttrs config [ "_module" ];
-
inherit (config) _module;
+
checked = builtins.seq checkUnmatched;
+
+
result = {
+
options = checked options;
+
config = checked (removeAttrs config [ "_module" ]);
+
_module = checked (config._module);
+
+
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;
+
};
};
in result;
+7
lib/tests/modules.sh
···
# 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
+28
lib/tests/modules/declare-submodule-via-evalModules.nix
···
+
{ 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;
+
}
+
];
+
}
+28 -28
lib/types.nix
···
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
+
# > and < 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 {
···
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