2013-10-28 00:56:22 +01:00
|
|
|
|
with import ./.. {};
|
|
|
|
|
with lib;
|
2009-07-13 18:18:52 +02:00
|
|
|
|
|
|
|
|
|
rec {
|
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
/* 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. */
|
2013-10-28 15:48:20 +01:00
|
|
|
|
evalModules = { modules, prefix ? [], args ? {}, check ? true }:
|
2009-07-13 18:18:52 +02:00
|
|
|
|
let
|
2013-10-28 00:56:22 +01:00
|
|
|
|
args' = args // result;
|
|
|
|
|
closed = closeModules modules args';
|
|
|
|
|
# Note: the list of modules is reversed to maintain backward
|
|
|
|
|
# compatibility with the old module system. Not sure if this is
|
|
|
|
|
# the most sensible policy.
|
2013-10-28 15:48:20 +01:00
|
|
|
|
options = mergeModules check prefix (reverseList closed);
|
2013-10-28 00:56:22 +01:00
|
|
|
|
config = yieldConfig options;
|
|
|
|
|
yieldConfig = mapAttrs (n: v: if isOption v then v.value else yieldConfig v);
|
|
|
|
|
result = { inherit options config; };
|
|
|
|
|
in result;
|
|
|
|
|
|
|
|
|
|
/* Close a set of modules under the ‘imports’ relation. */
|
|
|
|
|
closeModules = modules: args:
|
2013-06-07 09:42:46 +02:00
|
|
|
|
let
|
2013-10-28 14:25:58 +01:00
|
|
|
|
toClosureList = parent: imap (n: x:
|
2013-10-28 00:56:22 +01:00
|
|
|
|
if isAttrs x || builtins.isFunction x then
|
2013-10-28 14:25:58 +01:00
|
|
|
|
unifyModuleSyntax parent "anon-${toString n}" (applyIfFunction x args)
|
2013-10-28 00:56:22 +01:00
|
|
|
|
else
|
2013-10-28 14:25:58 +01:00
|
|
|
|
unifyModuleSyntax (toString x) (toString x) (applyIfFunction (import x) args));
|
2013-10-28 00:56:22 +01:00
|
|
|
|
in
|
|
|
|
|
builtins.genericClosure {
|
2013-10-28 14:25:58 +01:00
|
|
|
|
startSet = toClosureList unknownModule modules;
|
|
|
|
|
operator = m: toClosureList m.file m.imports;
|
2013-06-07 09:42:46 +02:00
|
|
|
|
};
|
2009-10-09 20:11:24 +02:00
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
/* Massage a module into canonical form, that is, a set consisting
|
|
|
|
|
of ‘options’, ‘config’ and ‘imports’ attributes. */
|
2013-10-28 04:46:36 +01:00
|
|
|
|
unifyModuleSyntax = file: key: m:
|
2013-10-28 00:56:22 +01:00
|
|
|
|
if m ? config || m ? options || m ? imports then
|
|
|
|
|
let badAttrs = removeAttrs m ["imports" "options" "config"]; in
|
|
|
|
|
if badAttrs != {} then
|
|
|
|
|
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. ${builtins.toXML m} "
|
|
|
|
|
else
|
2013-10-28 04:46:36 +01:00
|
|
|
|
{ inherit file key;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
imports = m.imports or [];
|
|
|
|
|
options = m.options or {};
|
|
|
|
|
config = m.config or {};
|
|
|
|
|
}
|
|
|
|
|
else
|
2013-10-28 04:46:36 +01:00
|
|
|
|
{ inherit file key;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
imports = m.require or [];
|
|
|
|
|
options = {};
|
2013-10-28 15:48:20 +01:00
|
|
|
|
config = removeAttrs m ["require"];
|
2013-10-28 00:56:22 +01:00
|
|
|
|
};
|
2009-10-09 20:11:24 +02:00
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
applyIfFunction = f: arg: if builtins.isFunction f then f arg else f;
|
|
|
|
|
|
|
|
|
|
/* Merge a list of modules. This will recurse over the option
|
|
|
|
|
declarations in all modules, combining them into a single set.
|
|
|
|
|
At the same time, for each option declaration, it will merge the
|
|
|
|
|
corresponding option definitions in all machines, returning them
|
|
|
|
|
in the ‘value’ attribute of each option. */
|
2013-10-28 15:48:20 +01:00
|
|
|
|
mergeModules = check: prefix: modules:
|
|
|
|
|
mergeModules' check prefix modules
|
2013-10-28 04:46:36 +01:00
|
|
|
|
(concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
|
2013-10-28 00:56:22 +01:00
|
|
|
|
|
2013-10-28 15:48:20 +01:00
|
|
|
|
mergeModules' = check: prefix: options: configs:
|
|
|
|
|
let
|
|
|
|
|
declaredNames = concatMap (m: attrNames m.options) options;
|
|
|
|
|
declaredNames' = listToAttrs (map (n: { name = n; value = 1; }) declaredNames);
|
|
|
|
|
checkDefinedNames = res: fold (m: res: fold (n: res:
|
|
|
|
|
if hasAttr n declaredNames' then res else
|
|
|
|
|
throw "The option `${showOption (prefix ++ [n])}' defined in `${m.file}' does not exist."
|
|
|
|
|
) res (attrNames m.config)) res configs;
|
|
|
|
|
in (if check then checkDefinedNames else id) (listToAttrs (map (name: {
|
2013-10-28 04:46:36 +01:00
|
|
|
|
# We're descending into attribute ‘name’.
|
|
|
|
|
inherit name;
|
|
|
|
|
value =
|
|
|
|
|
let
|
2013-10-28 14:25:58 +01:00
|
|
|
|
loc = prefix ++ [name];
|
2013-10-28 04:46:36 +01:00
|
|
|
|
# Get all submodules that declare ‘name’.
|
|
|
|
|
decls = concatLists (map (m:
|
2013-10-28 07:51:46 +01:00
|
|
|
|
if hasAttr name m.options
|
|
|
|
|
then [ { inherit (m) file; options = getAttr name m.options; } ]
|
|
|
|
|
else []
|
2013-10-28 04:46:36 +01:00
|
|
|
|
) options);
|
|
|
|
|
# Get all submodules that define ‘name’.
|
|
|
|
|
defns = concatLists (map (m:
|
2013-10-28 07:51:46 +01:00
|
|
|
|
if hasAttr name m.config
|
|
|
|
|
then map (config: { inherit (m) file; inherit config; })
|
|
|
|
|
(pushDownProperties (getAttr name m.config))
|
|
|
|
|
else []
|
2013-10-28 04:46:36 +01:00
|
|
|
|
) configs);
|
|
|
|
|
nrOptions = count (m: isOption m.options) decls;
|
2013-10-28 05:23:10 +01:00
|
|
|
|
# Process mkMerge and mkIf properties.
|
|
|
|
|
defns' = concatMap (m:
|
2013-10-28 07:51:46 +01:00
|
|
|
|
if hasAttr name m.config
|
|
|
|
|
then map (m': { inherit (m) file; value = m'; }) (dischargeProperties (getAttr name m.config))
|
|
|
|
|
else []
|
2013-10-28 04:46:36 +01:00
|
|
|
|
) configs;
|
|
|
|
|
in
|
|
|
|
|
if nrOptions == length decls then
|
2013-10-28 14:25:58 +01:00
|
|
|
|
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
|
|
|
|
in evalOptionValue loc opt defns'
|
2013-10-28 04:46:36 +01:00
|
|
|
|
else if nrOptions != 0 then
|
|
|
|
|
let
|
|
|
|
|
firstOption = findFirst (m: isOption m.options) "" decls;
|
|
|
|
|
firstNonOption = findFirst (m: !isOption m.options) "" decls;
|
|
|
|
|
in
|
2013-10-28 14:25:58 +01:00
|
|
|
|
throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
|
2013-10-28 04:46:36 +01:00
|
|
|
|
else
|
2013-10-28 15:48:20 +01:00
|
|
|
|
mergeModules' check loc decls defns;
|
|
|
|
|
}) declaredNames));
|
2013-10-28 00:56:22 +01:00
|
|
|
|
|
|
|
|
|
/* Merge multiple option declarations into a single declaration. In
|
|
|
|
|
general, there should be only one declaration of each option.
|
|
|
|
|
The exception is the ‘options’ attribute, which specifies
|
|
|
|
|
sub-options. These can be specified multiple times to allow one
|
|
|
|
|
module to add sub-options to an option declared somewhere else
|
|
|
|
|
(e.g. multiple modules define sub-options for ‘fileSystems’). */
|
|
|
|
|
mergeOptionDecls = loc: opts:
|
2013-10-28 04:46:36 +01:00
|
|
|
|
fold (opt: res:
|
|
|
|
|
if opt.options ? default && res ? default ||
|
|
|
|
|
opt.options ? example && res ? example ||
|
|
|
|
|
opt.options ? description && res ? description ||
|
2013-10-28 15:13:51 +01:00
|
|
|
|
opt.options ? merge && res ? merge || # FIXME: remove merge
|
2013-10-28 04:46:36 +01:00
|
|
|
|
opt.options ? apply && res ? apply ||
|
|
|
|
|
opt.options ? type && res ? type
|
2013-10-28 00:56:22 +01:00
|
|
|
|
then
|
2013-10-28 04:46:36 +01:00
|
|
|
|
throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${concatStringsSep " and " (map (d: "`${d}'") res.declarations)}."
|
2013-10-28 00:56:22 +01:00
|
|
|
|
else
|
2013-10-28 04:46:36 +01:00
|
|
|
|
opt.options // res //
|
|
|
|
|
{ declarations = [opt.file] ++ res.declarations;
|
2013-10-28 07:51:46 +01:00
|
|
|
|
options = if opt.options ? options then [(toList opt.options.options ++ res.options)] else [];
|
2013-10-28 04:46:36 +01:00
|
|
|
|
}
|
2013-10-28 14:25:58 +01:00
|
|
|
|
) { inherit loc; declarations = []; options = []; } opts;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
|
|
|
|
|
/* Merge all the definitions of an option to produce the final
|
|
|
|
|
config value. */
|
2013-10-28 05:23:10 +01:00
|
|
|
|
evalOptionValue = loc: opt: defs:
|
2009-09-14 15:19:00 +02:00
|
|
|
|
let
|
2013-10-28 00:56:22 +01:00
|
|
|
|
# Process mkOverride properties, adding in the default
|
|
|
|
|
# value specified in the option declaration (if any).
|
2013-10-28 07:51:46 +01:00
|
|
|
|
defsFinal = filterOverrides
|
|
|
|
|
((if opt ? default then [{ file = head opt.declarations; value = mkOptionDefault opt.default; }] else []) ++ defs);
|
2013-10-28 04:46:36 +01:00
|
|
|
|
# Type-check the remaining definitions, and merge them if
|
|
|
|
|
# possible.
|
2013-10-28 00:56:22 +01:00
|
|
|
|
merged =
|
|
|
|
|
if defsFinal == [] then
|
|
|
|
|
throw "The option `${showOption loc}' is used but not defined."
|
|
|
|
|
else
|
2013-10-28 04:46:36 +01:00
|
|
|
|
fold (def: res:
|
2013-10-28 05:23:10 +01:00
|
|
|
|
if opt.type.check def.value then res
|
2013-10-28 04:46:36 +01:00
|
|
|
|
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${opt.type.name}.")
|
2013-10-28 15:13:51 +01:00
|
|
|
|
(opt.type.merge' { prefix = loc; } (map (m: m.value) defsFinal)) defsFinal;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
# Finally, apply the ‘apply’ function to the merged
|
|
|
|
|
# value. This allows options to yield a value computed
|
|
|
|
|
# from the definitions.
|
|
|
|
|
value = (opt.apply or id) merged;
|
|
|
|
|
in opt //
|
2013-10-28 15:13:51 +01:00
|
|
|
|
{ value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
2013-10-28 16:24:48 +01:00
|
|
|
|
definitions = map (def: def.value) defsFinal;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
isDefined = defsFinal != [];
|
2011-04-27 20:41:37 +02:00
|
|
|
|
};
|
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
/* Given a config set, expand mkMerge properties, and push down the
|
|
|
|
|
mkIf properties into the children. The result is a list of
|
|
|
|
|
config sets that do not have properties at top-level. For
|
|
|
|
|
example,
|
2009-07-13 18:18:52 +02:00
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
|
2009-09-15 15:36:30 +02:00
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
is transformed into
|
2009-09-15 15:36:30 +02:00
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
[ { boot = set1; } { boot = mkIf cond set2; services mkIf cond set3; } ].
|
2012-11-30 12:56:18 +01:00
|
|
|
|
|
2013-10-28 00:56:22 +01:00
|
|
|
|
This transform is the critical step that allows mkIf conditions
|
|
|
|
|
to refer to the full configuration without creating an infinite
|
|
|
|
|
recursion.
|
|
|
|
|
*/
|
|
|
|
|
pushDownProperties = cfg:
|
|
|
|
|
if cfg._type or "" == "merge" then
|
|
|
|
|
concatMap pushDownProperties cfg.contents
|
|
|
|
|
else if cfg._type or "" == "if" then
|
|
|
|
|
map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
|
2012-04-16 01:31:48 +02:00
|
|
|
|
else
|
2013-10-28 00:56:22 +01:00
|
|
|
|
# FIXME: handle mkOverride?
|
|
|
|
|
[ cfg ];
|
|
|
|
|
|
|
|
|
|
/* Given a config value, expand mkMerge properties, and discharge
|
|
|
|
|
any mkIf conditions. That is, this is the place where mkIf
|
|
|
|
|
conditions are actually evaluated. The result is a list of
|
|
|
|
|
config values. For example, ‘mkIf false x’ yields ‘[]’,
|
|
|
|
|
‘mkIf true x’ yields ‘[x]’, and
|
|
|
|
|
|
|
|
|
|
mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
|
|
|
|
|
|
|
|
|
|
yields ‘[ 1 2 ]’.
|
|
|
|
|
*/
|
|
|
|
|
dischargeProperties = def:
|
|
|
|
|
if def._type or "" == "merge" then
|
|
|
|
|
concatMap dischargeProperties def.contents
|
|
|
|
|
else if def._type or "" == "if" then
|
|
|
|
|
if def.condition then
|
|
|
|
|
dischargeProperties def.content
|
|
|
|
|
else
|
|
|
|
|
[ ]
|
2009-10-10 01:03:24 +02:00
|
|
|
|
else
|
2013-10-28 00:56:22 +01:00
|
|
|
|
[ def ];
|
2009-09-15 15:36:30 +02:00
|
|
|
|
|
2013-10-28 07:52:24 +01:00
|
|
|
|
/* Given a list of config values, process the mkOverride properties,
|
|
|
|
|
that is, return the values that have the highest (that is,
|
|
|
|
|
numerically lowest) priority, and strip the mkOverride
|
2013-10-28 00:56:22 +01:00
|
|
|
|
properties. For example,
|
2012-11-30 12:56:18 +01:00
|
|
|
|
|
2013-10-28 07:52:24 +01:00
|
|
|
|
[ { file = "/1"; value = mkOverride 10 "a"; }
|
|
|
|
|
{ file = "/2"; value = mkOverride 20 "b"; }
|
|
|
|
|
{ file = "/3"; value = "z"; }
|
|
|
|
|
{ file = "/4"; value = mkOverride 10 "d"; }
|
2013-10-28 04:46:36 +01:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
yields
|
|
|
|
|
|
2013-10-28 07:52:24 +01:00
|
|
|
|
[ { file = "/1"; value = "a"; }
|
|
|
|
|
{ file = "/4"; value = "d"; }
|
2013-10-28 04:46:36 +01:00
|
|
|
|
]
|
2009-09-15 15:36:30 +02:00
|
|
|
|
|
2013-10-28 04:46:36 +01:00
|
|
|
|
Note that "z" has the default priority 100.
|
2013-10-28 00:56:22 +01:00
|
|
|
|
*/
|
|
|
|
|
filterOverrides = defs:
|
2009-09-15 15:36:30 +02:00
|
|
|
|
let
|
2013-10-28 00:56:22 +01:00
|
|
|
|
defaultPrio = 100;
|
2013-10-28 05:23:10 +01:00
|
|
|
|
getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
min = x: y: if x < y then x else y;
|
|
|
|
|
highestPrio = fold (def: prio: min (getPrio def) prio) 9999 defs;
|
2013-10-28 05:23:10 +01:00
|
|
|
|
strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
|
2013-10-28 00:56:22 +01:00
|
|
|
|
in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
|
|
|
|
|
|
|
|
|
|
/* Hack for backward compatibility: convert options of type
|
|
|
|
|
optionSet to configOf. FIXME: remove eventually. */
|
|
|
|
|
fixupOptionType = loc: opt:
|
2009-11-07 02:59:50 +01:00
|
|
|
|
let
|
2013-10-28 00:56:22 +01:00
|
|
|
|
options' = opt.options or
|
|
|
|
|
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute.");
|
|
|
|
|
coerce = x:
|
|
|
|
|
if builtins.isFunction x then x
|
|
|
|
|
else { config, ... }: { options = x; };
|
|
|
|
|
options = map coerce (flatten options');
|
|
|
|
|
f = tp:
|
|
|
|
|
if tp.name == "option set" then types.submodule options
|
|
|
|
|
else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options)
|
|
|
|
|
else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options)
|
|
|
|
|
else if tp.name == "list of option sets" then types.listOf (types.submodule options)
|
|
|
|
|
else if tp.name == "null or option set" then types.nullOr (types.submodule options)
|
|
|
|
|
else tp;
|
|
|
|
|
in opt // { type = f (opt.type or types.unspecified); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Properties. */
|
|
|
|
|
|
|
|
|
|
mkIf = condition: content:
|
|
|
|
|
{ _type = "if";
|
|
|
|
|
inherit condition content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkAssert = assertion: message: content:
|
|
|
|
|
mkIf
|
|
|
|
|
(if assertion then true else throw "\nFailed assertion: ${message}")
|
|
|
|
|
content;
|
|
|
|
|
|
|
|
|
|
mkMerge = contents:
|
|
|
|
|
{ _type = "merge";
|
|
|
|
|
inherit contents;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkOverride = priority: content:
|
|
|
|
|
{ _type = "override";
|
|
|
|
|
inherit priority content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkOptionDefault = mkOverride 1001;
|
|
|
|
|
mkDefault = mkOverride 1000;
|
|
|
|
|
mkForce = mkOverride 50;
|
|
|
|
|
|
|
|
|
|
mkFixStrictness = id; # obsolete, no-op
|
|
|
|
|
|
|
|
|
|
# FIXME: Add mkOrder back in. It's not currently used anywhere in
|
|
|
|
|
# NixOS, but it should be useful.
|
2009-11-07 02:59:50 +01:00
|
|
|
|
|
2009-09-14 22:10:41 +02:00
|
|
|
|
}
|