1with import ./lists.nix;
2with import ./trivial.nix;
3with import ./attrsets.nix;
4with import ./options.nix;
5with import ./debug.nix;
6with import ./types.nix;
7
8rec {
9
10 /* Evaluate a set of modules. The result is a set of two
11 attributes: ‘options’: the nested set of all option declarations,
12 and ‘config’: the nested set of all option values.
13 !!! Please think twice before adding to this argument list! The more
14 that is specified here instead of in the modules themselves the harder
15 it is to transparently move a set of modules to be a submodule of another
16 config (as the proper arguments need to be replicated at each call to
17 evalModules) and the less declarative the module set is. */
18 evalModules = { modules
19 , prefix ? []
20 , # This should only be used for special arguments that need to be evaluated
21 # when resolving module structure (like in imports). For everything else,
22 # there's _module.args.
23 specialArgs ? {}
24 , # This would be remove in the future, Prefer _module.args option instead.
25 args ? {}
26 , # This would be remove in the future, Prefer _module.check option instead.
27 check ? true
28 }:
29 let
30 # This internal module declare internal options under the `_module'
31 # attribute. These options are fragile, as they are used by the
32 # module system to change the interpretation of modules.
33 internalModule = rec {
34 _file = ./modules.nix;
35
36 key = _file;
37
38 options = {
39 _module.args = mkOption {
40 type = types.attrsOf types.unspecified;
41 internal = true;
42 description = "Arguments passed to each module.";
43 };
44
45 _module.check = mkOption {
46 type = types.bool;
47 internal = true;
48 default = check;
49 description = "Whether to check whether all option definitions have matching declarations.";
50 };
51 };
52
53 config = {
54 _module.args = args;
55 };
56 };
57
58 closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs);
59
60 # Note: the list of modules is reversed to maintain backward
61 # compatibility with the old module system. Not sure if this is
62 # the most sensible policy.
63 options = mergeModules prefix (reverseList closed);
64
65 # Traverse options and extract the option values into the final
66 # config set. At the same time, check whether all option
67 # definitions have matching declarations.
68 # !!! _module.check's value can't depend on any other config values
69 # without an infinite recursion. One way around this is to make the
70 # 'config' passed around to the modules be unconditionally unchecked,
71 # and only do the check in 'result'.
72 config = yieldConfig prefix options;
73 yieldConfig = prefix: set:
74 let res = removeAttrs (mapAttrs (n: v:
75 if isOption v then v.value
76 else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"];
77 in
78 if options._module.check.value && set ? _definedNames then
79 foldl' (res: m:
80 foldl' (res: name:
81 if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.")
82 res m.names)
83 res set._definedNames
84 else
85 res;
86 result = { inherit options config; };
87 in result;
88
89 /* Close a set of modules under the ‘imports’ relation. */
90 closeModules = modules: args:
91 let
92 toClosureList = file: parentKey: imap (n: x:
93 if isAttrs x || isFunction x then
94 let key = "${parentKey}:anon-${toString n}"; in
95 unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args)
96 else
97 let file = toString x; key = toString x; in
98 unifyModuleSyntax file key (applyIfFunction key (import x) args));
99 in
100 builtins.genericClosure {
101 startSet = toClosureList unknownModule "" modules;
102 operator = m: toClosureList m.file m.key m.imports;
103 };
104
105 /* Massage a module into canonical form, that is, a set consisting
106 of ‘options’, ‘config’ and ‘imports’ attributes. */
107 unifyModuleSyntax = file: key: m:
108 let metaSet = if m ? meta
109 then { meta = m.meta; }
110 else {};
111 in
112 if m ? config || m ? options then
113 let badAttrs = removeAttrs m ["imports" "options" "config" "key" "_file" "meta"]; in
114 if badAttrs != {} then
115 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
116 else
117 { file = m._file or file;
118 key = toString m.key or key;
119 imports = m.imports or [];
120 options = m.options or {};
121 config = mkMerge [ (m.config or {}) metaSet ];
122 }
123 else
124 { file = m._file or file;
125 key = toString m.key or key;
126 imports = m.require or [] ++ m.imports or [];
127 options = {};
128 config = mkMerge [ (removeAttrs m ["key" "_file" "require" "imports"]) metaSet ];
129 };
130
131 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
132 let
133 # Module arguments are resolved in a strict manner when attribute set
134 # deconstruction is used. As the arguments are now defined with the
135 # config._module.args option, the strictness used on the attribute
136 # set argument would cause an infinite loop, if the result of the
137 # option is given as argument.
138 #
139 # To work-around the strictness issue on the deconstruction of the
140 # attributes set argument, we create a new attribute set which is
141 # constructed to satisfy the expected set of attributes. Thus calling
142 # a module will resolve strictly the attributes used as argument but
143 # not their values. The values are forwarding the result of the
144 # evaluation of the option.
145 requiredArgs = builtins.attrNames (builtins.functionArgs f);
146 context = name: ''while evaluating the module argument `${name}' in "${key}":'';
147 extraArgs = builtins.listToAttrs (map (name: {
148 inherit name;
149 value = addErrorContext (context name)
150 (args.${name} or config._module.args.${name});
151 }) requiredArgs);
152
153 # Note: we append in the opposite order such that we can add an error
154 # context on the explicited arguments of "args" too. This update
155 # operator is used to make the "args@{ ... }: with args.lib;" notation
156 # works.
157 in f (args // extraArgs)
158 else
159 f;
160
161 /* We have to pack and unpack submodules. We cannot wrap the expected
162 result of the function as we would no longer be able to list the arguments
163 of the submodule. (see applyIfFunction) */
164 unpackSubmodule = unpack: m: args:
165 if isType "submodule" m then
166 { _file = m.file; } // (unpack m.submodule args)
167 else unpack m args;
168
169 packSubmodule = file: m:
170 { _type = "submodule"; file = file; submodule = m; };
171
172 /* Merge a list of modules. This will recurse over the option
173 declarations in all modules, combining them into a single set.
174 At the same time, for each option declaration, it will merge the
175 corresponding option definitions in all machines, returning them
176 in the ‘value’ attribute of each option. */
177 mergeModules = prefix: modules:
178 mergeModules' prefix modules
179 (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
180
181 mergeModules' = prefix: options: configs:
182 listToAttrs (map (name: {
183 # We're descending into attribute ‘name’.
184 inherit name;
185 value =
186 let
187 loc = prefix ++ [name];
188 # Get all submodules that declare ‘name’.
189 decls = concatMap (m:
190 if m.options ? ${name}
191 then [ { inherit (m) file; options = m.options.${name}; } ]
192 else []
193 ) options;
194 # Get all submodules that define ‘name’.
195 defns = concatMap (m:
196 if m.config ? ${name}
197 then map (config: { inherit (m) file; inherit config; })
198 (pushDownProperties m.config.${name})
199 else []
200 ) configs;
201 nrOptions = count (m: isOption m.options) decls;
202 # Extract the definitions for this loc
203 defns' = map (m: { inherit (m) file; value = m.config.${name}; })
204 (filter (m: m.config ? ${name}) configs);
205 in
206 if nrOptions == length decls then
207 let opt = fixupOptionType loc (mergeOptionDecls loc decls);
208 in evalOptionValue loc opt defns'
209 else if nrOptions != 0 then
210 let
211 firstOption = findFirst (m: isOption m.options) "" decls;
212 firstNonOption = findFirst (m: !isOption m.options) "" decls;
213 in
214 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
215 else
216 mergeModules' loc decls defns;
217 }) (concatMap (m: attrNames m.options) options))
218 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
219
220 /* Merge multiple option declarations into a single declaration. In
221 general, there should be only one declaration of each option.
222 The exception is the ‘options’ attribute, which specifies
223 sub-options. These can be specified multiple times to allow one
224 module to add sub-options to an option declared somewhere else
225 (e.g. multiple modules define sub-options for ‘fileSystems’).
226
227 'loc' is the list of attribute names where the option is located.
228
229 'opts' is a list of modules. Each module has an options attribute which
230 correspond to the definition of 'loc' in 'opt.file'. */
231 mergeOptionDecls = loc: opts:
232 foldl' (res: opt:
233 if opt.options ? default && res ? default ||
234 opt.options ? example && res ? example ||
235 opt.options ? description && res ? description ||
236 opt.options ? apply && res ? apply ||
237 # Accept to merge options which have identical types.
238 opt.options ? type && res ? type && opt.options.type.name != res.type.name
239 then
240 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
241 else
242 let
243 /* Add the modules of the current option to the list of modules
244 already collected. The options attribute except either a list of
245 submodules or a submodule. For each submodule, we add the file of the
246 current option declaration as the file use for the submodule. If the
247 submodule defines any filename, then we ignore the enclosing option file. */
248 options' = toList opt.options.options;
249 coerceOption = file: opt:
250 if isFunction opt then packSubmodule file opt
251 else packSubmodule file { options = opt; };
252 getSubModules = opt.options.type.getSubModules or null;
253 submodules =
254 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options
255 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options
256 else res.options;
257 in opt.options // res //
258 { declarations = res.declarations ++ [opt.file];
259 options = submodules;
260 }
261 ) { inherit loc; declarations = []; options = []; } opts;
262
263 /* Merge all the definitions of an option to produce the final
264 config value. */
265 evalOptionValue = loc: opt: defs:
266 let
267 # Add in the default value for this option, if any.
268 defs' =
269 (optional (opt ? default)
270 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
271
272 # Handle properties, check types, and merge everything together.
273 res =
274 if opt.readOnly or false && length defs' > 1 then
275 throw "The option `${showOption loc}' is read-only, but it's set multiple times."
276 else
277 mergeDefinitions loc opt.type defs';
278
279 # Check whether the option is defined, and apply the ‘apply’
280 # function to the merged value. This allows options to yield a
281 # value computed from the definitions.
282 value =
283 if !res.isDefined then
284 throw "The option `${showOption loc}' is used but not defined."
285 else if opt ? apply then
286 opt.apply res.mergedValue
287 else
288 res.mergedValue;
289
290 in opt //
291 { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
292 definitions = map (def: def.value) res.defsFinal;
293 files = map (def: def.file) res.defsFinal;
294 inherit (res) isDefined;
295 };
296
297 # Merge definitions of a value of a given type.
298 mergeDefinitions = loc: type: defs: rec {
299 defsFinal =
300 let
301 # Process mkMerge and mkIf properties.
302 defs' = concatMap (m:
303 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value)
304 ) defs;
305
306 # Process mkOverride properties.
307 defs'' = filterOverrides defs';
308
309 # Sort mkOrder properties.
310 defs''' =
311 # Avoid sorting if we don't have to.
312 if any (def: def.value._type or "" == "order") defs''
313 then sortProperties defs''
314 else defs'';
315 in defs''';
316
317 # Type-check the remaining definitions, and merge them.
318 mergedValue = foldl' (res: def:
319 if type.check def.value then res
320 else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.")
321 (type.merge loc defsFinal) defsFinal;
322
323 isDefined = defsFinal != [];
324
325 optionalValue =
326 if isDefined then { value = mergedValue; }
327 else {};
328 };
329
330 /* Given a config set, expand mkMerge properties, and push down the
331 other properties into the children. The result is a list of
332 config sets that do not have properties at top-level. For
333 example,
334
335 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
336
337 is transformed into
338
339 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
340
341 This transform is the critical step that allows mkIf conditions
342 to refer to the full configuration without creating an infinite
343 recursion.
344 */
345 pushDownProperties = cfg:
346 if cfg._type or "" == "merge" then
347 concatMap pushDownProperties cfg.contents
348 else if cfg._type or "" == "if" then
349 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
350 else if cfg._type or "" == "override" then
351 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
352 else # FIXME: handle mkOrder?
353 [ cfg ];
354
355 /* Given a config value, expand mkMerge properties, and discharge
356 any mkIf conditions. That is, this is the place where mkIf
357 conditions are actually evaluated. The result is a list of
358 config values. For example, ‘mkIf false x’ yields ‘[]’,
359 ‘mkIf true x’ yields ‘[x]’, and
360
361 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
362
363 yields ‘[ 1 2 ]’.
364 */
365 dischargeProperties = def:
366 if def._type or "" == "merge" then
367 concatMap dischargeProperties def.contents
368 else if def._type or "" == "if" then
369 if def.condition then
370 dischargeProperties def.content
371 else
372 [ ]
373 else
374 [ def ];
375
376 /* Given a list of config values, process the mkOverride properties,
377 that is, return the values that have the highest (that is,
378 numerically lowest) priority, and strip the mkOverride
379 properties. For example,
380
381 [ { file = "/1"; value = mkOverride 10 "a"; }
382 { file = "/2"; value = mkOverride 20 "b"; }
383 { file = "/3"; value = "z"; }
384 { file = "/4"; value = mkOverride 10 "d"; }
385 ]
386
387 yields
388
389 [ { file = "/1"; value = "a"; }
390 { file = "/4"; value = "d"; }
391 ]
392
393 Note that "z" has the default priority 100.
394 */
395 filterOverrides = defs:
396 let
397 defaultPrio = 100;
398 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio;
399 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
400 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
401 in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
402
403 /* Sort a list of properties. The sort priority of a property is
404 1000 by default, but can be overriden by wrapping the property
405 using mkOrder. */
406 sortProperties = defs:
407 let
408 strip = def:
409 if def.value._type or "" == "order"
410 then def // { value = def.value.content; inherit (def.value) priority; }
411 else def;
412 defs' = map strip defs;
413 compare = a: b: (a.priority or 1000) < (b.priority or 1000);
414 in sort compare defs';
415
416 /* Hack for backward compatibility: convert options of type
417 optionSet to options of type submodule. FIXME: remove
418 eventually. */
419 fixupOptionType = loc: opt:
420 let
421 options = opt.options or
422 (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
423 f = tp:
424 if tp.name == "option set" || tp.name == "submodule" then
425 throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
426 else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options)
427 else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options)
428 else if tp.name == "list of option sets" then types.listOf (types.submodule options)
429 else if tp.name == "null or option set" then types.nullOr (types.submodule options)
430 else tp;
431 in
432 if opt.type.getSubModules or null == null
433 then opt // { type = f (opt.type or types.unspecified); }
434 else opt // { type = opt.type.substSubModules opt.options; options = []; };
435
436
437 /* Properties. */
438
439 mkIf = condition: content:
440 { _type = "if";
441 inherit condition content;
442 };
443
444 mkAssert = assertion: message: content:
445 mkIf
446 (if assertion then true else throw "\nFailed assertion: ${message}")
447 content;
448
449 mkMerge = contents:
450 { _type = "merge";
451 inherit contents;
452 };
453
454 mkOverride = priority: content:
455 { _type = "override";
456 inherit priority content;
457 };
458
459 mkOptionDefault = mkOverride 1001; # priority of option defaults
460 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
461 mkForce = mkOverride 50;
462 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
463
464 mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0);
465
466 mkFixStrictness = id; # obsolete, no-op
467
468 mkOrder = priority: content:
469 { _type = "order";
470 inherit priority content;
471 };
472
473 mkBefore = mkOrder 500;
474 mkAfter = mkOrder 1500;
475
476
477 # Convenient property used to transfer all definitions and their
478 # properties from one option to another. This property is useful for
479 # renaming options, and also for including properties from another module
480 # system, including sub-modules.
481 #
482 # { config, options, ... }:
483 #
484 # {
485 # # 'bar' might not always be defined in the current module-set.
486 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
487 #
488 # # 'barbaz' has to be defined in the current module-set.
489 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
490 # }
491 #
492 # Note, this is different than taking the value of the option and using it
493 # as a definition, as the new definition will not keep the mkOverride /
494 # mkDefault properties of the previous option.
495 #
496 mkAliasDefinitions = mkAliasAndWrapDefinitions id;
497 mkAliasAndWrapDefinitions = wrap: option:
498 mkMerge
499 (optional (isOption option && option.isDefined)
500 (wrap (mkMerge option.definitions)));
501
502
503 /* Compatibility. */
504 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
505
506
507 /* Return a module that causes a warning to be shown if the
508 specified option is defined. For example,
509
510 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
511
512 causes a warning if the user defines boot.loader.grub.bootDevice.
513
514 replacementInstructions is a string that provides instructions on
515 how to achieve the same functionality without the removed option,
516 or alternatively a reasoning why the functionality is not needed.
517 replacementInstructions SHOULD be provided!
518 */
519 mkRemovedOptionModule = optionName: replacementInstructions:
520 { options, ... }:
521 { options = setAttrByPath optionName (mkOption {
522 visible = false;
523 });
524 config.warnings =
525 let opt = getAttrFromPath optionName options; in
526 optional opt.isDefined ''
527 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
528 ${replacementInstructions}'';
529 };
530
531 /* Return a module that causes a warning to be shown if the
532 specified "from" option is defined; the defined value is however
533 forwarded to the "to" option. This can be used to rename options
534 while providing backward compatibility. For example,
535
536 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
537
538 forwards any definitions of boot.copyKernels to
539 boot.loader.grub.copyKernels while printing a warning.
540 */
541 mkRenamedOptionModule = from: to: doRename {
542 inherit from to;
543 visible = false;
544 warn = true;
545 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
546 };
547
548 /* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */
549 mkAliasOptionModule = from: to: doRename {
550 inherit from to;
551 visible = true;
552 warn = false;
553 use = id;
554 };
555
556 doRename = { from, to, visible, warn, use }:
557 let
558 toOf = attrByPath to
559 (abort "Renaming error: option `${showOption to}' does not exists.");
560 in
561 { config, options, ... }:
562 { options = setAttrByPath from (mkOption {
563 description = "Alias of <option>${showOption to}</option>.";
564 apply = x: use (toOf config);
565 });
566 config = {
567 warnings =
568 let opt = getAttrFromPath from options; in
569 optional (warn && opt.isDefined)
570 "The option `${showOption from}' defined in ${showFiles opt.files} has been renamed to `${showOption to}'.";
571 } // setAttrByPath to (mkAliasDefinitions (getAttrFromPath from options));
572 };
573
574}