Merge pull request #143207 from hercules-ci/evalModules-add-extendModules-and-type
lib.evalModules: Add extendModules and type to result
This commit is contained in:
commit
3d76af212d
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
28
lib/tests/modules/declare-submodule-via-evalModules.nix
Normal file
28
lib/tests/modules/declare-submodule-via-evalModules.nix
Normal file
@ -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;
|
||||
}
|
||||
];
|
||||
}
|
@ -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
|
||||
# > 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 {
|
||||
@ -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
|
||||
# > 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.
|
||||
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
|
||||
|
@ -61,7 +61,7 @@ in rec {
|
||||
args = extraArgs;
|
||||
specialArgs =
|
||||
{ modulesPath = builtins.toString ../modules; } // specialArgs;
|
||||
}) config options _module;
|
||||
}) config options _module type;
|
||||
|
||||
# These are the extra arguments passed to every module. In
|
||||
# particular, Nixpkgs is passed through the "pkgs" argument.
|
||||
|
Loading…
Reference in New Issue
Block a user