Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 53 additions & 9 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -120,7 +144,9 @@ rec {
};

config = {
_module.args = args;
_module.args = {
inherit extendModules;
} // args;
};
};

Expand Down Expand Up @@ -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;

Expand Down
10 changes: 7 additions & 3 deletions lib/options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions lib/tests/modules/declare-submodule-via-evalModules.nix
Original file line number Diff line number Diff line change
@@ -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;
}
];
}
56 changes: 28 additions & 28 deletions lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -505,50 +505,50 @@ 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 {
name = "submodule";
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
Expand Down
2 changes: 1 addition & 1 deletion nixos/lib/eval-config.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down