forked from mirrors/nixpkgs
Keep position information for option declarations and definitions
Also, when an option definition fails to type-check, print the file name of the module in which the offending definition occurs, e.g. error: user-thrown exception: The option value `boot.loader.grub.version' in `/etc/nixos/configuration.nix' is not a integer.
This commit is contained in:
parent
cfab329437
commit
40913958a2
|
@ -240,7 +240,7 @@ rec {
|
||||||
# names, hopefully this does not affect the system because the maximal
|
# names, hopefully this does not affect the system because the maximal
|
||||||
# laziness avoid computing twice the same expression and listToAttrs does
|
# laziness avoid computing twice the same expression and listToAttrs does
|
||||||
# not care about duplicated attribute names.
|
# not care about duplicated attribute names.
|
||||||
zipAttrsWith = f: sets: zipWithNames (concatMap attrNames sets) f sets;
|
zipAttrsWith = f: sets: zipAttrsWithNames (concatMap attrNames sets) f sets;
|
||||||
|
|
||||||
zipAttrs = zipAttrsWith (name: values: values);
|
zipAttrs = zipAttrsWith (name: values: values);
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,11 @@ in rec {
|
||||||
all = pred: fold (x: y: if pred x then y else false) true;
|
all = pred: fold (x: y: if pred x then y else false) true;
|
||||||
|
|
||||||
|
|
||||||
|
# Count how many times function `pred' returns true for the elements
|
||||||
|
# of `list'.
|
||||||
|
count = pred: fold (x: c: if pred x then inc c else c) 0;
|
||||||
|
|
||||||
|
|
||||||
# Return a singleton list or an empty list, depending on a boolean
|
# Return a singleton list or an empty list, depending on a boolean
|
||||||
# value. Useful when building lists with optional elements
|
# value. Useful when building lists with optional elements
|
||||||
# (e.g. `++ optional (system == "i686-linux") flashplayer').
|
# (e.g. `++ optional (system == "i686-linux") flashplayer').
|
||||||
|
|
122
lib/modules.nix
122
lib/modules.nix
|
@ -24,9 +24,9 @@ rec {
|
||||||
let
|
let
|
||||||
coerceToModule = n: x:
|
coerceToModule = n: x:
|
||||||
if isAttrs x || builtins.isFunction x then
|
if isAttrs x || builtins.isFunction x then
|
||||||
unifyModuleSyntax "anon-${toString n}" (applyIfFunction x args)
|
unifyModuleSyntax "<unknown-file>" "anon-${toString n}" (applyIfFunction x args)
|
||||||
else
|
else
|
||||||
unifyModuleSyntax (toString x) (applyIfFunction (import x) args);
|
unifyModuleSyntax (toString x) (toString x) (applyIfFunction (import x) args);
|
||||||
toClosureList = imap (path: coerceToModule path);
|
toClosureList = imap (path: coerceToModule path);
|
||||||
in
|
in
|
||||||
builtins.genericClosure {
|
builtins.genericClosure {
|
||||||
|
@ -36,19 +36,19 @@ rec {
|
||||||
|
|
||||||
/* Massage a module into canonical form, that is, a set consisting
|
/* Massage a module into canonical form, that is, a set consisting
|
||||||
of ‘options’, ‘config’ and ‘imports’ attributes. */
|
of ‘options’, ‘config’ and ‘imports’ attributes. */
|
||||||
unifyModuleSyntax = key: m:
|
unifyModuleSyntax = file: key: m:
|
||||||
if m ? config || m ? options || m ? imports then
|
if m ? config || m ? options || m ? imports then
|
||||||
let badAttrs = removeAttrs m ["imports" "options" "config"]; in
|
let badAttrs = removeAttrs m ["imports" "options" "config"]; in
|
||||||
if badAttrs != {} then
|
if badAttrs != {} then
|
||||||
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. ${builtins.toXML m} "
|
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. ${builtins.toXML m} "
|
||||||
else
|
else
|
||||||
{ inherit key;
|
{ inherit file key;
|
||||||
imports = m.imports or [];
|
imports = m.imports or [];
|
||||||
options = m.options or {};
|
options = m.options or {};
|
||||||
config = m.config or {};
|
config = m.config or {};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ inherit key;
|
{ inherit file key;
|
||||||
imports = m.require or [];
|
imports = m.require or [];
|
||||||
options = {};
|
options = {};
|
||||||
config = m;
|
config = m;
|
||||||
|
@ -62,19 +62,47 @@ rec {
|
||||||
corresponding option definitions in all machines, returning them
|
corresponding option definitions in all machines, returning them
|
||||||
in the ‘value’ attribute of each option. */
|
in the ‘value’ attribute of each option. */
|
||||||
mergeModules = modules:
|
mergeModules = modules:
|
||||||
mergeModules' [] (map (m: m.options) modules) (concatMap (m: pushDownProperties m.config) modules);
|
mergeModules' [] modules
|
||||||
|
(concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
|
||||||
|
|
||||||
mergeModules' = loc: options: configs:
|
mergeModules' = loc: options: configs:
|
||||||
zipAttrsWith (name: vals:
|
let names = concatMap (m: attrNames m.options) options;
|
||||||
let loc' = loc ++ [name]; in
|
in listToAttrs (map (name: {
|
||||||
if all isOption vals then
|
# We're descending into attribute ‘name’.
|
||||||
let opt = fixupOptionType loc' (mergeOptionDecls loc' vals);
|
inherit name;
|
||||||
in evalOptionValue loc' opt (catAttrs name configs)
|
value =
|
||||||
else if any isOption vals then
|
let
|
||||||
throw "There are options with the prefix `${showOption loc'}', which is itself an option."
|
loc' = loc ++ [name];
|
||||||
else
|
# Get all submodules that declare ‘name’.
|
||||||
mergeModules' loc' vals (concatMap pushDownProperties (catAttrs name configs))
|
decls = concatLists (map (m:
|
||||||
) options;
|
optional (hasAttr name m.options)
|
||||||
|
{ inherit (m) file; options = getAttr name m.options; }
|
||||||
|
) options);
|
||||||
|
# Get all submodules that define ‘name’.
|
||||||
|
defns = concatLists (map (m:
|
||||||
|
optionals (hasAttr name m.config)
|
||||||
|
(map (config: { inherit (m) file; inherit config; })
|
||||||
|
(pushDownProperties (getAttr name m.config)))
|
||||||
|
) configs);
|
||||||
|
nrOptions = count (m: isOption m.options) decls;
|
||||||
|
|
||||||
|
defns2 = concatMap (m:
|
||||||
|
optional (hasAttr name m.config)
|
||||||
|
{ inherit (m) file; config = getAttr name m.config; }
|
||||||
|
) configs;
|
||||||
|
in
|
||||||
|
if nrOptions == length decls then
|
||||||
|
let opt = fixupOptionType loc' (mergeOptionDecls loc' decls);
|
||||||
|
in evalOptionValue loc' opt defns2
|
||||||
|
else if nrOptions != 0 then
|
||||||
|
let
|
||||||
|
firstOption = findFirst (m: isOption m.options) "" decls;
|
||||||
|
firstNonOption = findFirst (m: !isOption m.options) "" decls;
|
||||||
|
in
|
||||||
|
throw "The option `${showOption loc'}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
|
||||||
|
else
|
||||||
|
mergeModules' loc' decls defns;
|
||||||
|
}) names);
|
||||||
|
|
||||||
/* Merge multiple option declarations into a single declaration. In
|
/* Merge multiple option declarations into a single declaration. In
|
||||||
general, there should be only one declaration of each option.
|
general, there should be only one declaration of each option.
|
||||||
|
@ -83,41 +111,41 @@ rec {
|
||||||
module to add sub-options to an option declared somewhere else
|
module to add sub-options to an option declared somewhere else
|
||||||
(e.g. multiple modules define sub-options for ‘fileSystems’). */
|
(e.g. multiple modules define sub-options for ‘fileSystems’). */
|
||||||
mergeOptionDecls = loc: opts:
|
mergeOptionDecls = loc: opts:
|
||||||
fold (opt1: opt2:
|
fold (opt: res:
|
||||||
if opt1 ? default && opt2 ? default ||
|
if opt.options ? default && res ? default ||
|
||||||
opt1 ? example && opt2 ? example ||
|
opt.options ? example && res ? example ||
|
||||||
opt1 ? description && opt2 ? description ||
|
opt.options ? description && res ? description ||
|
||||||
opt1 ? merge && opt2 ? merge ||
|
opt.options ? merge && res ? merge ||
|
||||||
opt1 ? apply && opt2 ? apply ||
|
opt.options ? apply && res ? apply ||
|
||||||
opt1 ? type && opt2 ? type
|
opt.options ? type && res ? type
|
||||||
then
|
then
|
||||||
throw "Conflicting declarations of the option `${showOption loc}'."
|
throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${concatStringsSep " and " (map (d: "`${d}'") res.declarations)}."
|
||||||
else
|
else
|
||||||
opt1 // opt2
|
opt.options // res //
|
||||||
// optionalAttrs (opt1 ? options && opt2 ? options)
|
{ declarations = [opt.file] ++ res.declarations;
|
||||||
{ options = [ opt1.options opt2.options ]; }
|
options = optionals (opt.options ? options) (toList opt.options.options ++ res.options);
|
||||||
) {} opts;
|
}
|
||||||
|
) { declarations = []; options = []; } opts;
|
||||||
|
|
||||||
/* Merge all the definitions of an option to produce the final
|
/* Merge all the definitions of an option to produce the final
|
||||||
config value. */
|
config value. */
|
||||||
evalOptionValue = loc: opt: defs:
|
evalOptionValue = loc: opt: cfgs:
|
||||||
let
|
let
|
||||||
# Process mkMerge and mkIf properties.
|
# Process mkMerge and mkIf properties.
|
||||||
defs' = concatMap dischargeProperties defs;
|
defs' = concatMap (m: map (config: { inherit (m) file; inherit config; }) (dischargeProperties m.config)) cfgs;
|
||||||
# Process mkOverride properties, adding in the default
|
# Process mkOverride properties, adding in the default
|
||||||
# value specified in the option declaration (if any).
|
# value specified in the option declaration (if any).
|
||||||
defsFinal = filterOverrides (optional (opt ? default) (mkOptionDefault opt.default) ++ defs');
|
defsFinal = filterOverrides (optional (opt ? default) ({ file = head opt.declarations; config = mkOptionDefault opt.default; }) ++ defs');
|
||||||
# Type-check the remaining definitions, and merge them
|
# Type-check the remaining definitions, and merge them if
|
||||||
# if possible.
|
# possible.
|
||||||
merged =
|
merged =
|
||||||
if defsFinal == [] then
|
if defsFinal == [] then
|
||||||
throw "The option `${showOption loc}' is used but not defined."
|
throw "The option `${showOption loc}' is used but not defined."
|
||||||
else
|
else
|
||||||
if all opt.type.check defsFinal then
|
fold (def: res:
|
||||||
opt.type.merge defsFinal
|
if opt.type.check def.config then res
|
||||||
#throw "The option `${showOption loc}' has multiple values (with no way to merge them)."
|
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${opt.type.name}.")
|
||||||
else
|
(opt.type.merge (map (m: m.config) defsFinal)) defsFinal;
|
||||||
throw "A value of the option `${showOption loc}' has a bad type.";
|
|
||||||
# Finally, apply the ‘apply’ function to the merged
|
# Finally, apply the ‘apply’ function to the merged
|
||||||
# value. This allows options to yield a value computed
|
# value. This allows options to yield a value computed
|
||||||
# from the definitions.
|
# from the definitions.
|
||||||
|
@ -178,17 +206,27 @@ rec {
|
||||||
is,. numerically lowest) priority, and strip the mkOverride
|
is,. numerically lowest) priority, and strip the mkOverride
|
||||||
properties. For example,
|
properties. For example,
|
||||||
|
|
||||||
[ (mkOverride 10 "a") (mkOverride 20 "b") "z" (mkOverride 10 "d") ]
|
[ { file = "/1"; config = mkOverride 10 "a"; }
|
||||||
|
{ file = "/2"; config = mkOverride 20 "b"; }
|
||||||
|
{ file = "/3"; config = "z"; }
|
||||||
|
{ file = "/4"; config = mkOverride 10 "d"; }
|
||||||
|
]
|
||||||
|
|
||||||
yields ‘[ "a" "d" ]’. Note that "z" has the default priority 100.
|
yields
|
||||||
|
|
||||||
|
[ { file = "/1"; config = "a"; }
|
||||||
|
{ file = "/4"; config = "d"; }
|
||||||
|
]
|
||||||
|
|
||||||
|
Note that "z" has the default priority 100.
|
||||||
*/
|
*/
|
||||||
filterOverrides = defs:
|
filterOverrides = defs:
|
||||||
let
|
let
|
||||||
defaultPrio = 100;
|
defaultPrio = 100;
|
||||||
getPrio = def: if def._type or "" == "override" then def.priority else defaultPrio;
|
getPrio = def: if def.config._type or "" == "override" then def.config.priority else defaultPrio;
|
||||||
min = x: y: if x < y then x else y;
|
min = x: y: if x < y then x else y;
|
||||||
highestPrio = fold (def: prio: min (getPrio def) prio) 9999 defs;
|
highestPrio = fold (def: prio: min (getPrio def) prio) 9999 defs;
|
||||||
strip = def: if def._type or "" == "override" then def.content else def;
|
strip = def: if def.config._type or "" == "override" then def // { config = def.config.content; } else def;
|
||||||
in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
|
in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
|
||||||
|
|
||||||
/* Hack for backward compatibility: convert options of type
|
/* Hack for backward compatibility: convert options of type
|
||||||
|
|
Loading…
Reference in a new issue