aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-28 04:46:36 +0100
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-28 22:45:55 +0100
commit40913958a2c61e49348d2038ae6f2a1d32c126fe (patch)
tree02929d83462f084b245bcc7ebb948fbafc9b4c50
parentcfab329437a24811104bc8daa2b83b80614f753c (diff)
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.
-rw-r--r--lib/attrsets.nix2
-rw-r--r--lib/lists.nix5
-rw-r--r--lib/modules.nix122
3 files changed, 86 insertions, 43 deletions
diff --git a/lib/attrsets.nix b/lib/attrsets.nix
index 01d51779c809..40bc1667b854 100644
--- a/lib/attrsets.nix
+++ b/lib/attrsets.nix
@@ -240,7 +240,7 @@ rec {
# names, hopefully this does not affect the system because the maximal
# laziness avoid computing twice the same expression and listToAttrs does
# 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);
diff --git a/lib/lists.nix b/lib/lists.nix
index eb7e6baf5ed8..d0b09539bf69 100644
--- a/lib/lists.nix
+++ b/lib/lists.nix
@@ -118,6 +118,11 @@ in rec {
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
# value. Useful when building lists with optional elements
# (e.g. `++ optional (system == "i686-linux") flashplayer').
diff --git a/lib/modules.nix b/lib/modules.nix
index f5a82d7d8e9d..6161a6a0ee58 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -24,9 +24,9 @@ rec {
let
coerceToModule = n: x:
if isAttrs x || builtins.isFunction x then
- unifyModuleSyntax "anon-${toString n}" (applyIfFunction x args)
+ unifyModuleSyntax "<unknown-file>" "anon-${toString n}" (applyIfFunction x args)
else
- unifyModuleSyntax (toString x) (applyIfFunction (import x) args);
+ unifyModuleSyntax (toString x) (toString x) (applyIfFunction (import x) args);
toClosureList = imap (path: coerceToModule path);
in
builtins.genericClosure {
@@ -36,19 +36,19 @@ rec {
/* Massage a module into canonical form, that is, a set consisting
of ‘options’, ‘config’ and ‘imports’ attributes. */
- unifyModuleSyntax = key: m:
+ unifyModuleSyntax = file: key: m:
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
- { inherit key;
+ { inherit file key;
imports = m.imports or [];
options = m.options or {};
config = m.config or {};
}
else
- { inherit key;
+ { inherit file key;
imports = m.require or [];
options = {};
config = m;
@@ -62,19 +62,47 @@ rec {
corresponding option definitions in all machines, returning them
in the ‘value’ attribute of each option. */
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:
- zipAttrsWith (name: vals:
- let loc' = loc ++ [name]; in
- if all isOption vals then
- let opt = fixupOptionType loc' (mergeOptionDecls loc' vals);
- in evalOptionValue loc' opt (catAttrs name configs)
- else if any isOption vals then
- throw "There are options with the prefix `${showOption loc'}', which is itself an option."
- else
- mergeModules' loc' vals (concatMap pushDownProperties (catAttrs name configs))
- ) options;
+ let names = concatMap (m: attrNames m.options) options;
+ in listToAttrs (map (name: {
+ # We're descending into attribute ‘name’.
+ inherit name;
+ value =
+ let
+ loc' = loc ++ [name];
+ # Get all submodules that declare ‘name’.
+ decls = concatLists (map (m:
+ 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
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
(e.g. multiple modules define sub-options for ‘fileSystems’). */
mergeOptionDecls = loc: opts:
- fold (opt1: opt2:
- if opt1 ? default && opt2 ? default ||
- opt1 ? example && opt2 ? example ||
- opt1 ? description && opt2 ? description ||
- opt1 ? merge && opt2 ? merge ||
- opt1 ? apply && opt2 ? apply ||
- opt1 ? type && opt2 ? type
+ fold (opt: res:
+ if opt.options ? default && res ? default ||
+ opt.options ? example && res ? example ||
+ opt.options ? description && res ? description ||
+ opt.options ? merge && res ? merge ||
+ opt.options ? apply && res ? apply ||
+ opt.options ? type && res ? type
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
- opt1 // opt2
- // optionalAttrs (opt1 ? options && opt2 ? options)
- { options = [ opt1.options opt2.options ]; }
- ) {} opts;
+ opt.options // res //
+ { declarations = [opt.file] ++ res.declarations;
+ options = optionals (opt.options ? options) (toList opt.options.options ++ res.options);
+ }
+ ) { declarations = []; options = []; } opts;
/* Merge all the definitions of an option to produce the final
config value. */
- evalOptionValue = loc: opt: defs:
+ evalOptionValue = loc: opt: cfgs:
let
# 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
# value specified in the option declaration (if any).
- defsFinal = filterOverrides (optional (opt ? default) (mkOptionDefault opt.default) ++ defs');
- # Type-check the remaining definitions, and merge them
- # if possible.
+ defsFinal = filterOverrides (optional (opt ? default) ({ file = head opt.declarations; config = mkOptionDefault opt.default; }) ++ defs');
+ # Type-check the remaining definitions, and merge them if
+ # possible.
merged =
if defsFinal == [] then
throw "The option `${showOption loc}' is used but not defined."
else
- if all opt.type.check defsFinal then
- opt.type.merge defsFinal
- #throw "The option `${showOption loc}' has multiple values (with no way to merge them)."
- else
- throw "A value of the option `${showOption loc}' has a bad type.";
+ fold (def: res:
+ if opt.type.check def.config then res
+ else throw "The option value `${showOption loc}' in `${def.file}' is not a ${opt.type.name}.")
+ (opt.type.merge (map (m: m.config) defsFinal)) defsFinal;
# Finally, apply the ‘apply’ function to the merged
# value. This allows options to yield a value computed
# from the definitions.
@@ -178,17 +206,27 @@ rec {
is,. numerically lowest) priority, and strip the mkOverride
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
+
+ [ { file = "/1"; config = "a"; }
+ { file = "/4"; config = "d"; }
+ ]
- yields ‘[ "a" "d" ]’. Note that "z" has the default priority 100.
+ Note that "z" has the default priority 100.
*/
filterOverrides = defs:
let
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;
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;
/* Hack for backward compatibility: convert options of type