nixpkgs/pkgs/lib/modules.nix

288 lines
8.2 KiB
Nix
Raw Normal View History

# NixOS module handling.
let lib = import ./default.nix; in
with { inherit (builtins) head tail; };
with import ./trivial.nix;
with import ./lists.nix;
with import ./misc.nix;
with import ./attrsets.nix;
with import ./options.nix;
with import ./properties.nix;
rec {
# Unfortunately this can also be a string.
isPath = x: !(
builtins.isFunction x
|| builtins.isAttrs x
|| builtins.isInt x
|| builtins.isBool x
|| builtins.isList x
);
importIfPath = path:
if isPath path then
import path
else
path;
applyIfFunction = f: arg:
if builtins.isFunction f then
f arg
else
f;
isModule = m:
(m ? config && isAttrs m.config && ! isOption m.config)
|| (m ? options && isAttrs m.options && ! isOption m.options);
# Convert module to a set which has imports / options and config
# attributes.
unifyModuleSyntax = m:
let
getImports = m:
if m ? config || m ? options then
attrByPath ["imports"] [] m
else
toList (rmProperties (attrByPath ["require"] [] (delayProperties m)));
getImportedPaths = m: filter isPath (getImports m);
getImportedSets = m: filter (x: !isPath x) (getImports m);
getConfig = m:
removeAttrs (delayProperties m) ["require"];
in
if isModule m then
{ key = "<unknown location>"; } // m
else
{
key = "<unknown location>";
imports = getImportedPaths m;
config = getConfig m;
} // (
if getImportedSets m != [] then
assert tail (getImportedSets m) == [];
{ options = head (getImportedSets m); }
else
{}
);
moduleClosure = initModules: args:
let
moduleImport = m:
(unifyModuleSyntax (applyIfFunction (importIfPath m) args)) // {
# used by generic closure to avoid duplicated imports.
key = if isPath m then m else "<unknown location>";
};
getImports = m: attrByPath ["imports"] [] m;
in
(lazyGenericClosure {
startSet = map moduleImport (filter isPath initModules);
operator = m: map moduleImport (getImports m);
}) ++ (map moduleImport (filter (m: ! isPath m) initModules));
selectDeclsAndDefs = modules:
lib.concatMap (m:
if m ? config || m ? options then
[ (attrByPath ["options"] {} m) ]
++ [ (attrByPath ["config"] {} m) ]
else
[ m ]
) modules;
moduleApply = funs: module:
lib.mapAttrs (name: value:
if builtins.hasAttr name funs then
let fun = lib.getAttr name funs; in
fun value
else
value
) module;
delayModule = module:
moduleApply { config = delayProperties; } module;
selectModule = name: m:
{ inherit (m) key;
} // (
if m ? options && builtins.hasAttr name m.options then
{ options = lib.getAttr name m.options; }
else {}
) // (
if m ? config && builtins.hasAttr name m.config then
{ config = lib.getAttr name m.config; }
else {}
);
filterModules = name: modules:
filter (m: m ? config || m ? options) (
map (selectModule name) modules
);
modulesNames = modules:
lib.concatMap (m: []
++ optionals (m ? options) (lib.attrNames m.options)
++ optionals (m ? config) (lib.attrNames m.config)
) modules;
moduleZip = funs: modules:
lib.mapAttrs (name: fun:
fun (
map (lib.getAttr name) (
filter (builtins.hasAttr name) modules
)
)
) funs;
moduleMerge = path: modules:
let modules_ = modules; in
let
addName = name:
if path == "" then name else path + "." + name;
modules = map delayModule modules_;
modulesOf = name: filterModules name modules;
declarationsOf = name: filter (m: m ? options) (modulesOf name);
definitionsOf = name: filter (m: m ? config ) (modulesOf name);
recurseInto = name: modules:
moduleMerge (addName name) (modulesOf name);
recurseForOption = name: modules:
moduleMerge name (
map unifyModuleSyntax modules
);
errorSource = modules:
"The error may come from the following files:\n" + (
lib.concatStringsSep "\n" (
map (m:
if m ? key then toString m.key else "<unknown location>"
) modules
)
);
eol = "\n";
errDefinedWithoutDeclaration = name:
let
badModules =
filter (m: ! isAttrs m.config)
(definitionsOf name);
in
"${eol
}Option '${addName name}' defined without option declaration.${eol
}${errorSource badModules}${eol
}";
endRecursion = { options = {}; config = {}; };
in if modules == [] then endRecursion else
lib.fix (result:
moduleZip {
options = lib.zip (name: values:
if any isOption values then
let
# locations to sub-options declarations
decls =
map (m:
mapSubOptions (subModule:
let module = lib.applyIfFunction subModule {}; in
if lib.isModule module then
{ inherit (m) key; } // subModule
else
args: {
inherit (m) key;
options = lib.applyIfFunction subModule args;
}
) m.options
) (declarationsOf name);
in
addOptionMakeUp
{ name = addName name; recurseInto = recurseForOption; }
(mergeOptionDecls decls)
// {
declarations =
map (m: {
source = m.key;
}) (declarationsOf name);
definitions =
map (m: {
source = m.key;
}) (definitionsOf name);
config = builtins.tryEval
(builtins.toXML (lib.getAttr name result.config));
}
else if all isAttrs values then
(recurseInto name modules).options
else
throw "${eol
}Unexpected type where option declarations are expected.${eol
}${errorSource (declarationsOf name)}${eol
}"
);
config = lib.zipWithNames (modulesNames modules) (name: values:
let
hasOpt = builtins.hasAttr name result.options;
opt = lib.getAttr name result.options;
in if hasOpt && isOption opt then
let defs = evalProperties values; in
lib.addErrorContext "${eol
}while evaluating the option '${addName name}'.${eol
}${errorSource (modulesOf name)}${eol
}" (
opt.apply (
if defs == [] then
if opt ? default then opt.default
else throw "Not defined."
else opt.merge defs
)
)
else if hasOpt && lib.attrNames opt == [] then
throw (errDefinedWithoutDeclaration name)
else if any (v: isOption (rmProperties v)) values then
let
badModules =
filter (m: isOption m.config)
(definitionsOf name);
in
throw "${eol
}Option ${addName name} is defined in the configuration section.${eol
}${errorSource badModules}${eol
}"
else if all isAttrs values then
(recurseInto name modules).config
else
throw (errDefinedWithoutDeclaration name)
);
} modules
);
fixMergeModules = initModules: {...}@args:
lib.fix (result:
# This trick avoid an infinite loop because names of attribute are
# know and it is not require to evaluate the result of moduleMerge to
# know which attribute are present as argument.
let module = { inherit (result) options config; }; in
moduleMerge "" (
moduleClosure initModules (module // args)
)
);
}