mergeModules'
lib.mergeModules'
Docs pulled from | This Revision | 29 minutes ago
Contribute
Enhance the ecosystem with your expertise! Contribute to fill the gaps in documentation. Your input can make a difference.
Noogle detected
Implementation
The following is the current implementation of this function.
prefix: modules: configs:
let
# an attrset 'name' => list of submodules that declare ‘name’.
declsByName = zipAttrs (
map (
module:
let
subtree = module.options;
in
if !(isAttrs subtree) then
throw ''
An option declaration for `${concatStringsSep "." prefix}' has type
`${typeOf subtree}' rather than an attribute set.
Did you mean to define this outside of `options'?
''
else
mapAttrs (n: option: {
inherit (module) _file;
pos = unsafeGetAttrPos n subtree;
options = option;
}) subtree
) modules
);
# The root of any module definition must be an attrset.
checkedConfigs =
assert all (
c:
# TODO: I have my doubts that this error would occur when option definitions are not matched.
# The implementation of this check used to be tied to a superficially similar check for
# options, so maybe that's why this is here.
isAttrs c.config
|| throw ''
In module `${c.file}', you're trying to define a value of type `${typeOf c.config}'
rather than an attribute set for the option
`${concatStringsSep "." prefix}'!
This usually happens if `${concatStringsSep "." prefix}' has option
definitions inside that are not matched. Please check how to properly define
this option by e.g. referring to `man 5 configuration.nix'!
''
) configs;
configs;
# an attrset 'name' => list of submodules that define ‘name’.
pushedDownDefinitionsByName = zipAttrsWith (n: concatLists) (
map (
module:
mapAttrs (
n: value:
map (config: {
inherit (module) file;
inherit config;
}) (pushDownProperties value)
) module.config
) checkedConfigs
);
# extract the definitions for each loc
rawDefinitionsByName = zipAttrs (
map (
module:
mapAttrs (n: value: {
inherit (module) file;
inherit value;
}) module.config
) checkedConfigs
);
# Convert an option tree decl to a submodule option decl
optionTreeToOption =
decl:
if isOption decl.options then
decl
else
decl
// {
options = mkOption {
type = types.submoduleWith {
modules = [ { options = decl.options; } ];
# `null` is not intended for use by modules. It is an internal
# value that means "whatever the user has declared elsewhere".
# This might become obsolete with https://github.com/NixOS/nixpkgs/issues/162398
shorthandOnlyDefinesConfig = null;
};
};
};
resultsByName = mapAttrs (
name: decls:
# We're descending into attribute ‘name’.
let
loc = prefix ++ [ name ];
defns = pushedDownDefinitionsByName.${name} or [ ];
defns' = rawDefinitionsByName.${name} or [ ];
optionDecls = filter (
m:
m.options ? _type
&& (m.options._type == "option" || throwDeclarationTypeError loc m.options._type m._file)
) decls;
in
if length optionDecls == length decls then
let
opt = fixupOptionType loc (mergeOptionDecls loc decls);
in
{
matchedOptions = evalOptionValue loc opt defns';
unmatchedDefns = [ ];
}
else if optionDecls != [ ] then
if
all (x: x.options.type.name or null == "submodule") optionDecls
# Raw options can only be merged into submodules. Merging into
# attrsets might be nice, but ambiguous. Suppose we have
# attrset as a `attrsOf submodule`. User declares option
# attrset.foo.bar, this could mean:
# a. option `bar` is only available in `attrset.foo`
# b. option `foo.bar` is available in all `attrset.*`
# c. reject and require "<name>" as a reminder that it behaves like (b).
# d. magically combine (a) and (c).
# All of the above are merely syntax sugar though.
then
let
opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls));
in
{
matchedOptions = evalOptionValue loc opt defns';
unmatchedDefns = [ ];
}
else
let
nonOptions = filter (m: !isOption m.options) decls;
in
throw "The option `${showOption loc}' in module `${(head optionDecls)._file}' would be a parent of the following options, but its type `${
(head optionDecls).options.type.description or "<no description>"
}' does not support nested options.\n${showRawDecls loc nonOptions}"
else
mergeModules' loc decls defns
) declsByName;
matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
# an attrset 'name' => list of unmatched definitions for 'name'
unmatchedDefnsByName =
# Propagate all unmatched definitions from nested option sets
mapAttrs (n: v: v.unmatchedDefns) resultsByName
# Plus the definitions for the current prefix that don't have a matching option
// removeAttrs rawDefinitionsByName (attrNames matchedOptions);
in
{
inherit matchedOptions;
# Transforms unmatchedDefnsByName into a list of definitions
unmatchedDefns =
if configs == [ ] then
# When no config values exist, there can be no unmatched config, so
# we short circuit and avoid evaluating more _options_ than necessary.
[ ]
else
concatLists (
mapAttrsToList (
name: defs:
map (
def:
def
// {
# Set this so we know when the definition first left unmatched territory
prefix = [ name ] ++ (def.prefix or [ ]);
}
) defs
) unmatchedDefnsByName
);
}