1{ lib }:
2
3let
4 inherit (lib)
5 all
6 any
7 attrByPath
8 attrNames
9 catAttrs
10 concatLists
11 concatMap
12 concatStringsSep
13 elem
14 filter
15 findFirst
16 foldl'
17 getAttrFromPath
18 head
19 id
20 imap1
21 isAttrs
22 isBool
23 isFunction
24 isList
25 isString
26 length
27 mapAttrs
28 mapAttrsToList
29 mapAttrsRecursiveCond
30 min
31 optional
32 optionalAttrs
33 optionalString
34 recursiveUpdate
35 reverseList sort
36 setAttrByPath
37 toList
38 types
39 warnIf
40 zipAttrsWith
41 ;
42 inherit (lib.options)
43 isOption
44 mkOption
45 showDefs
46 showFiles
47 showOption
48 unknownModule
49 literalExpression
50 ;
51
52 showDeclPrefix = loc: decl: prefix:
53 " - option(s) with prefix `${showOption (loc ++ [prefix])}' in module `${decl._file}'";
54 showRawDecls = loc: decls:
55 concatStringsSep "\n"
56 (sort (a: b: a < b)
57 (concatMap
58 (decl: map
59 (showDeclPrefix loc decl)
60 (attrNames decl.options)
61 )
62 decls
63 ));
64
65in
66
67rec {
68
69 /*
70 Evaluate a set of modules. The result is a set with the attributes:
71
72 ‘options’: The nested set of all option declarations,
73
74 ‘config’: The nested set of all option values.
75
76 ‘type’: A module system type representing the module set as a submodule,
77 to be extended by configuration from the containing module set.
78
79 This is also available as the module argument ‘moduleType’.
80
81 ‘extendModules’: A function similar to ‘evalModules’ but building on top
82 of the module set. Its arguments, ‘modules’ and ‘specialArgs’ are
83 added to the existing values.
84
85 Using ‘extendModules’ a few times has no performance impact as long
86 as you only reference the final ‘options’ and ‘config’.
87 If you do reference multiple ‘config’ (or ‘options’) from before and
88 after ‘extendModules’, performance is the same as with multiple
89 ‘evalModules’ invocations, because the new modules' ability to
90 override existing configuration fundamentally requires a new
91 fixpoint to be constructed.
92
93 This is also available as a module argument.
94
95 ‘_module’: A portion of the configuration tree which is elided from
96 ‘config’. It contains some values that are mostly internal to the
97 module system implementation.
98
99 !!! Please think twice before adding to this argument list! The more
100 that is specified here instead of in the modules themselves the harder
101 it is to transparently move a set of modules to be a submodule of another
102 config (as the proper arguments need to be replicated at each call to
103 evalModules) and the less declarative the module set is. */
104 evalModules = evalModulesArgs@
105 { modules
106 , prefix ? []
107 , # This should only be used for special arguments that need to be evaluated
108 # when resolving module structure (like in imports). For everything else,
109 # there's _module.args. If specialArgs.modulesPath is defined it will be
110 # used as the base path for disabledModules.
111 specialArgs ? {}
112 , # This would be remove in the future, Prefer _module.args option instead.
113 args ? {}
114 , # This would be remove in the future, Prefer _module.check option instead.
115 check ? true
116 }:
117 let
118 withWarnings = x:
119 lib.warnIf (evalModulesArgs?args) "The args argument to evalModules is deprecated. Please set config._module.args instead."
120 lib.warnIf (evalModulesArgs?check) "The check argument to evalModules is deprecated. Please set config._module.check instead."
121 x;
122
123 legacyModules =
124 optional (evalModulesArgs?args) {
125 config = {
126 _module.args = args;
127 };
128 }
129 ++ optional (evalModulesArgs?check) {
130 config = {
131 _module.check = mkDefault check;
132 };
133 };
134 regularModules = modules ++ legacyModules;
135
136 # This internal module declare internal options under the `_module'
137 # attribute. These options are fragile, as they are used by the
138 # module system to change the interpretation of modules.
139 #
140 # When extended with extendModules or moduleType, a fresh instance of
141 # this module is used, to avoid conflicts and allow chaining of
142 # extendModules.
143 internalModule = rec {
144 _file = "lib/modules.nix";
145
146 key = _file;
147
148 options = {
149 _module.args = mkOption {
150 # Because things like `mkIf` are entirely useless for
151 # `_module.args` (because there's no way modules can check which
152 # arguments were passed), we'll use `lazyAttrsOf` which drops
153 # support for that, in turn it's lazy in its values. This means e.g.
154 # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
155 # start a download when `pkgs` wasn't evaluated.
156 type = types.lazyAttrsOf types.raw;
157 # Only render documentation once at the root of the option tree,
158 # not for all individual submodules.
159 # Allow merging option decls to make this internal regardless.
160 ${if prefix == []
161 then null # unset => visible
162 else "internal"} = true;
163 # TODO: Change the type of this option to a submodule with a
164 # freeformType, so that individual arguments can be documented
165 # separately
166 description = ''
167 Additional arguments passed to each module in addition to ones
168 like <literal>lib</literal>, <literal>config</literal>,
169 and <literal>pkgs</literal>, <literal>modulesPath</literal>.
170 </para>
171 <para>
172 This option is also available to all submodules. Submodules do not
173 inherit args from their parent module, nor do they provide args to
174 their parent module or sibling submodules. The sole exception to
175 this is the argument <literal>name</literal> which is provided by
176 parent modules to a submodule and contains the attribute name
177 the submodule is bound to, or a unique generated name if it is
178 not bound to an attribute.
179 </para>
180 <para>
181 Some arguments are already passed by default, of which the
182 following <emphasis>cannot</emphasis> be changed with this option:
183 <itemizedlist>
184 <listitem>
185 <para>
186 <varname>lib</varname>: The nixpkgs library.
187 </para>
188 </listitem>
189 <listitem>
190 <para>
191 <varname>config</varname>: The results of all options after merging the values from all modules together.
192 </para>
193 </listitem>
194 <listitem>
195 <para>
196 <varname>options</varname>: The options declared in all modules.
197 </para>
198 </listitem>
199 <listitem>
200 <para>
201 <varname>specialArgs</varname>: The <literal>specialArgs</literal> argument passed to <literal>evalModules</literal>.
202 </para>
203 </listitem>
204 <listitem>
205 <para>
206 All attributes of <varname>specialArgs</varname>
207 </para>
208 <para>
209 Whereas option values can generally depend on other option values
210 thanks to laziness, this does not apply to <literal>imports</literal>, which
211 must be computed statically before anything else.
212 </para>
213 <para>
214 For this reason, callers of the module system can provide <literal>specialArgs</literal>
215 which are available during import resolution.
216 </para>
217 <para>
218 For NixOS, <literal>specialArgs</literal> includes
219 <varname>modulesPath</varname>, which allows you to import
220 extra modules from the nixpkgs package tree without having to
221 somehow make the module aware of the location of the
222 <literal>nixpkgs</literal> or NixOS directories.
223 <programlisting>
224 { modulesPath, ... }: {
225 imports = [
226 (modulesPath + "/profiles/minimal.nix")
227 ];
228 }
229 </programlisting>
230 </para>
231 </listitem>
232 </itemizedlist>
233 </para>
234 <para>
235 For NixOS, the default value for this option includes at least this argument:
236 <itemizedlist>
237 <listitem>
238 <para>
239 <varname>pkgs</varname>: The nixpkgs package set according to
240 the <option>nixpkgs.pkgs</option> option.
241 </para>
242 </listitem>
243 </itemizedlist>
244 '';
245 };
246
247 _module.check = mkOption {
248 type = types.bool;
249 internal = true;
250 default = true;
251 description = "Whether to check whether all option definitions have matching declarations.";
252 };
253
254 _module.freeformType = mkOption {
255 type = types.nullOr types.optionType;
256 internal = true;
257 default = null;
258 description = ''
259 If set, merge all definitions that don't have an associated option
260 together using this type. The result then gets combined with the
261 values of all declared options to produce the final <literal>
262 config</literal> value.
263
264 If this is <literal>null</literal>, definitions without an option
265 will throw an error unless <option>_module.check</option> is
266 turned off.
267 '';
268 };
269
270 _module.specialArgs = mkOption {
271 readOnly = true;
272 internal = true;
273 description = ''
274 Externally provided module arguments that can't be modified from
275 within a configuration, but can be used in module imports.
276 '';
277 };
278 };
279
280 config = {
281 _module.args = {
282 inherit extendModules;
283 moduleType = type;
284 };
285 _module.specialArgs = specialArgs;
286 };
287 };
288
289 merged =
290 let collected = collectModules
291 (specialArgs.modulesPath or "")
292 (regularModules ++ [ internalModule ])
293 ({ inherit lib options config specialArgs; } // specialArgs);
294 in mergeModules prefix (reverseList collected);
295
296 options = merged.matchedOptions;
297
298 config =
299 let
300
301 # For definitions that have an associated option
302 declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options;
303
304 # If freeformType is set, this is for definitions that don't have an associated option
305 freeformConfig =
306 let
307 defs = map (def: {
308 file = def.file;
309 value = setAttrByPath def.prefix def.value;
310 }) merged.unmatchedDefns;
311 in if defs == [] then {}
312 else declaredConfig._module.freeformType.merge prefix defs;
313
314 in if declaredConfig._module.freeformType == null then declaredConfig
315 # Because all definitions that had an associated option ended in
316 # declaredConfig, freeformConfig can only contain the non-option
317 # paths, meaning recursiveUpdate will never override any value
318 else recursiveUpdate freeformConfig declaredConfig;
319
320 checkUnmatched =
321 if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then
322 let
323 firstDef = head merged.unmatchedDefns;
324 baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}";
325 in
326 if attrNames options == [ "_module" ]
327 then
328 let
329 optionName = showOption prefix;
330 in
331 if optionName == ""
332 then throw ''
333 ${baseMsg}
334
335 It seems as if you're trying to declare an option by placing it into `config' rather than `options'!
336 ''
337 else
338 throw ''
339 ${baseMsg}
340
341 However there are no options defined in `${showOption prefix}'. Are you sure you've
342 declared your options properly? This can happen if you e.g. declared your options in `types.submodule'
343 under `config' rather than `options'.
344 ''
345 else throw baseMsg
346 else null;
347
348 checked = builtins.seq checkUnmatched;
349
350 extendModules = extendArgs@{
351 modules ? [],
352 specialArgs ? {},
353 prefix ? [],
354 }:
355 evalModules (evalModulesArgs // {
356 modules = regularModules ++ modules;
357 specialArgs = evalModulesArgs.specialArgs or {} // specialArgs;
358 prefix = extendArgs.prefix or evalModulesArgs.prefix or [];
359 });
360
361 type = lib.types.submoduleWith {
362 inherit modules specialArgs;
363 };
364
365 result = withWarnings {
366 options = checked options;
367 config = checked (removeAttrs config [ "_module" ]);
368 _module = checked (config._module);
369 inherit extendModules type;
370 };
371 in result;
372
373 # collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
374 #
375 # Collects all modules recursively through `import` statements, filtering out
376 # all modules in disabledModules.
377 collectModules = let
378
379 # Like unifyModuleSyntax, but also imports paths and calls functions if necessary
380 loadModule = args: fallbackFile: fallbackKey: m:
381 if isFunction m || isAttrs m then
382 unifyModuleSyntax fallbackFile fallbackKey (applyModuleArgsIfFunction fallbackKey m args)
383 else if isList m then
384 let defs = [{ file = fallbackFile; value = m; }]; in
385 throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
386 else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
387
388 /*
389 Collects all modules recursively into the form
390
391 {
392 disabled = [ <list of disabled modules> ];
393 # All modules of the main module list
394 modules = [
395 {
396 key = <key1>;
397 module = <module for key1>;
398 # All modules imported by the module for key1
399 modules = [
400 {
401 key = <key1-1>;
402 module = <module for key1-1>;
403 # All modules imported by the module for key1-1
404 modules = [ ... ];
405 }
406 ...
407 ];
408 }
409 ...
410 ];
411 }
412 */
413 collectStructuredModules =
414 let
415 collectResults = modules: {
416 disabled = concatLists (catAttrs "disabled" modules);
417 inherit modules;
418 };
419 in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x:
420 let
421 module = loadModule args parentFile "${parentKey}:anon-${toString n}" x;
422 collectedImports = collectStructuredModules module._file module.key module.imports args;
423 in {
424 key = module.key;
425 module = module;
426 modules = collectedImports.modules;
427 disabled = module.disabledModules ++ collectedImports.disabled;
428 }) initialModules);
429
430 # filterModules :: String -> { disabled, modules } -> [ Module ]
431 #
432 # Filters a structure as emitted by collectStructuredModules by removing all disabled
433 # modules recursively. It returns the final list of unique-by-key modules
434 filterModules = modulesPath: { disabled, modules }:
435 let
436 moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m;
437 disabledKeys = map moduleKey disabled;
438 keyFilter = filter (attrs: ! elem attrs.key disabledKeys);
439 in map (attrs: attrs.module) (builtins.genericClosure {
440 startSet = keyFilter modules;
441 operator = attrs: keyFilter attrs.modules;
442 });
443
444 in modulesPath: initialModules: args:
445 filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
446
447 /* Wrap a module with a default location for reporting errors. */
448 setDefaultModuleLocation = file: m:
449 { _file = file; imports = [ m ]; };
450
451 /* Massage a module into canonical form, that is, a set consisting
452 of ‘options’, ‘config’ and ‘imports’ attributes. */
453 unifyModuleSyntax = file: key: m:
454 let
455 addMeta = config: if m ? meta
456 then mkMerge [ config { meta = m.meta; } ]
457 else config;
458 addFreeformType = config: if m ? freeformType
459 then mkMerge [ config { _module.freeformType = m.freeformType; } ]
460 else config;
461 in
462 if m ? config || m ? options then
463 let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in
464 if badAttrs != {} then
465 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
466 else
467 { _file = toString m._file or file;
468 key = toString m.key or key;
469 disabledModules = m.disabledModules or [];
470 imports = m.imports or [];
471 options = m.options or {};
472 config = addFreeformType (addMeta (m.config or {}));
473 }
474 else
475 lib.throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module."
476 { _file = toString m._file or file;
477 key = toString m.key or key;
478 disabledModules = m.disabledModules or [];
479 imports = m.require or [] ++ m.imports or [];
480 options = {};
481 config = addFreeformType (addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"]));
482 };
483
484 applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
485 let
486 # Module arguments are resolved in a strict manner when attribute set
487 # deconstruction is used. As the arguments are now defined with the
488 # config._module.args option, the strictness used on the attribute
489 # set argument would cause an infinite loop, if the result of the
490 # option is given as argument.
491 #
492 # To work-around the strictness issue on the deconstruction of the
493 # attributes set argument, we create a new attribute set which is
494 # constructed to satisfy the expected set of attributes. Thus calling
495 # a module will resolve strictly the attributes used as argument but
496 # not their values. The values are forwarding the result of the
497 # evaluation of the option.
498 context = name: ''while evaluating the module argument `${name}' in "${key}":'';
499 extraArgs = builtins.mapAttrs (name: _:
500 builtins.addErrorContext (context name)
501 (args.${name} or config._module.args.${name})
502 ) (lib.functionArgs f);
503
504 # Note: we append in the opposite order such that we can add an error
505 # context on the explicited arguments of "args" too. This update
506 # operator is used to make the "args@{ ... }: with args.lib;" notation
507 # works.
508 in f (args // extraArgs)
509 else
510 f;
511
512 /* Merge a list of modules. This will recurse over the option
513 declarations in all modules, combining them into a single set.
514 At the same time, for each option declaration, it will merge the
515 corresponding option definitions in all machines, returning them
516 in the ‘value’ attribute of each option.
517
518 This returns a set like
519 {
520 # A recursive set of options along with their final values
521 matchedOptions = {
522 foo = { _type = "option"; value = "option value of foo"; ... };
523 bar.baz = { _type = "option"; value = "option value of bar.baz"; ... };
524 ...
525 };
526 # A list of definitions that weren't matched by any option
527 unmatchedDefns = [
528 { file = "file.nix"; prefix = [ "qux" ]; value = "qux"; }
529 ...
530 ];
531 }
532 */
533 mergeModules = prefix: modules:
534 mergeModules' prefix modules
535 (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules);
536
537 mergeModules' = prefix: options: configs:
538 let
539 /* byName is like foldAttrs, but will look for attributes to merge in the
540 specified attribute name.
541
542 byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"])
543 [
544 {
545 hidden="baz";
546 foo={qux="bar"; gla="flop";};
547 }
548 {
549 hidden="fli";
550 foo={qux="gne"; gli="flip";};
551 }
552 ]
553 ===>
554 {
555 gla = [ "module.hidden=baz,value=flop" ];
556 gli = [ "module.hidden=fli,value=flip" ];
557 qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ];
558 }
559 */
560 byName = attr: f: modules:
561 zipAttrsWith (n: concatLists)
562 (map (module: let subtree = module.${attr}; in
563 if !(builtins.isAttrs subtree) then
564 throw ''
565 You're trying to declare a value of type `${builtins.typeOf subtree}'
566 rather than an attribute-set for the option
567 `${builtins.concatStringsSep "." prefix}'!
568
569 This usually happens if `${builtins.concatStringsSep "." prefix}' has option
570 definitions inside that are not matched. Please check how to properly define
571 this option by e.g. referring to `man 5 configuration.nix'!
572 ''
573 else
574 mapAttrs (n: f module) subtree
575 ) modules);
576 # an attrset 'name' => list of submodules that declare ‘name’.
577 declsByName = byName "options" (module: option:
578 [{ inherit (module) _file; options = option; }]
579 ) options;
580 # an attrset 'name' => list of submodules that define ‘name’.
581 defnsByName = byName "config" (module: value:
582 map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
583 ) configs;
584 # extract the definitions for each loc
585 defnsByName' = byName "config" (module: value:
586 [{ inherit (module) file; inherit value; }]
587 ) configs;
588
589 # Convert an option tree decl to a submodule option decl
590 optionTreeToOption = decl:
591 if isOption decl.options
592 then decl
593 else decl // {
594 options = mkOption {
595 type = types.submoduleWith {
596 modules = [ { options = decl.options; } ];
597 # `null` is not intended for use by modules. It is an internal
598 # value that means "whatever the user has declared elsewhere".
599 # This might become obsolete with https://github.com/NixOS/nixpkgs/issues/162398
600 shorthandOnlyDefinesConfig = null;
601 };
602 };
603 };
604
605 resultsByName = mapAttrs (name: decls:
606 # We're descending into attribute ‘name’.
607 let
608 loc = prefix ++ [name];
609 defns = defnsByName.${name} or [];
610 defns' = defnsByName'.${name} or [];
611 optionDecls = filter (m: isOption m.options) decls;
612 in
613 if length optionDecls == length decls then
614 let opt = fixupOptionType loc (mergeOptionDecls loc decls);
615 in {
616 matchedOptions = evalOptionValue loc opt defns';
617 unmatchedDefns = [];
618 }
619 else if optionDecls != [] then
620 if all (x: x.options.type.name == "submodule") optionDecls
621 # Raw options can only be merged into submodules. Merging into
622 # attrsets might be nice, but ambiguous. Suppose we have
623 # attrset as a `attrsOf submodule`. User declares option
624 # attrset.foo.bar, this could mean:
625 # a. option `bar` is only available in `attrset.foo`
626 # b. option `foo.bar` is available in all `attrset.*`
627 # c. reject and require "<name>" as a reminder that it behaves like (b).
628 # d. magically combine (a) and (c).
629 # All of the above are merely syntax sugar though.
630 then
631 let opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls));
632 in {
633 matchedOptions = evalOptionValue loc opt defns';
634 unmatchedDefns = [];
635 }
636 else
637 let
638 firstNonOption = findFirst (m: !isOption m.options) "" decls;
639 nonOptions = filter (m: !isOption m.options) decls;
640 in
641 throw "The option `${showOption loc}' in module `${(lib.head optionDecls)._file}' would be a parent of the following options, but its type `${(lib.head optionDecls).options.type.description or "<no description>"}' does not support nested options.\n${
642 showRawDecls loc nonOptions
643 }"
644 else
645 mergeModules' loc decls defns) declsByName;
646
647 matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
648
649 # an attrset 'name' => list of unmatched definitions for 'name'
650 unmatchedDefnsByName =
651 # Propagate all unmatched definitions from nested option sets
652 mapAttrs (n: v: v.unmatchedDefns) resultsByName
653 # Plus the definitions for the current prefix that don't have a matching option
654 // removeAttrs defnsByName' (attrNames matchedOptions);
655 in {
656 inherit matchedOptions;
657
658 # Transforms unmatchedDefnsByName into a list of definitions
659 unmatchedDefns =
660 if configs == []
661 then
662 # When no config values exist, there can be no unmatched config, so
663 # we short circuit and avoid evaluating more _options_ than necessary.
664 []
665 else
666 concatLists (mapAttrsToList (name: defs:
667 map (def: def // {
668 # Set this so we know when the definition first left unmatched territory
669 prefix = [name] ++ (def.prefix or []);
670 }) defs
671 ) unmatchedDefnsByName);
672 };
673
674 /* Merge multiple option declarations into a single declaration. In
675 general, there should be only one declaration of each option.
676 The exception is the ‘options’ attribute, which specifies
677 sub-options. These can be specified multiple times to allow one
678 module to add sub-options to an option declared somewhere else
679 (e.g. multiple modules define sub-options for ‘fileSystems’).
680
681 'loc' is the list of attribute names where the option is located.
682
683 'opts' is a list of modules. Each module has an options attribute which
684 correspond to the definition of 'loc' in 'opt.file'. */
685 mergeOptionDecls =
686 let
687 coerceOption = file: opt:
688 if isFunction opt then setDefaultModuleLocation file opt
689 else setDefaultModuleLocation file { options = opt; };
690 in loc: opts:
691 foldl' (res: opt:
692 let t = res.type;
693 t' = opt.options.type;
694 mergedType = t.typeMerge t'.functor;
695 typesMergeable = mergedType != null;
696 typeSet = if (bothHave "type") && typesMergeable
697 then { type = mergedType; }
698 else {};
699 bothHave = k: opt.options ? ${k} && res ? ${k};
700 in
701 if bothHave "default" ||
702 bothHave "example" ||
703 bothHave "description" ||
704 bothHave "apply" ||
705 (bothHave "type" && (! typesMergeable))
706 then
707 throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
708 else
709 let
710 getSubModules = opt.options.type.getSubModules or null;
711 submodules =
712 if getSubModules != null then map (setDefaultModuleLocation opt._file) getSubModules ++ res.options
713 else res.options;
714 in opt.options // res //
715 { declarations = res.declarations ++ [opt._file];
716 options = submodules;
717 } // typeSet
718 ) { inherit loc; declarations = []; options = []; } opts;
719
720 /* Merge all the definitions of an option to produce the final
721 config value. */
722 evalOptionValue = loc: opt: defs:
723 let
724 # Add in the default value for this option, if any.
725 defs' =
726 (optional (opt ? default)
727 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
728
729 # Handle properties, check types, and merge everything together.
730 res =
731 if opt.readOnly or false && length defs' > 1 then
732 let
733 # For a better error message, evaluate all readOnly definitions as
734 # if they were the only definition.
735 separateDefs = map (def: def // {
736 value = (mergeDefinitions loc opt.type [ def ]).mergedValue;
737 }) defs';
738 in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
739 else
740 mergeDefinitions loc opt.type defs';
741
742 # Apply the 'apply' function to the merged value. This allows options to
743 # yield a value computed from the definitions
744 value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
745
746 warnDeprecation =
747 warnIf (opt.type.deprecationMessage != null)
748 "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}";
749
750 in warnDeprecation opt //
751 { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
752 inherit (res.defsFinal') highestPrio;
753 definitions = map (def: def.value) res.defsFinal;
754 files = map (def: def.file) res.defsFinal;
755 inherit (res) isDefined;
756 # This allows options to be correctly displayed using `${options.path.to.it}`
757 __toString = _: showOption loc;
758 };
759
760 # Merge definitions of a value of a given type.
761 mergeDefinitions = loc: type: defs: rec {
762 defsFinal' =
763 let
764 # Process mkMerge and mkIf properties.
765 defs' = concatMap (m:
766 map (value: { inherit (m) file; inherit value; }) (builtins.addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
767 ) defs;
768
769 # Process mkOverride properties.
770 defs'' = filterOverrides' defs';
771
772 # Sort mkOrder properties.
773 defs''' =
774 # Avoid sorting if we don't have to.
775 if any (def: def.value._type or "" == "order") defs''.values
776 then sortProperties defs''.values
777 else defs''.values;
778 in {
779 values = defs''';
780 inherit (defs'') highestPrio;
781 };
782 defsFinal = defsFinal'.values;
783
784 # Type-check the remaining definitions, and merge them. Or throw if no definitions.
785 mergedValue =
786 if isDefined then
787 if all (def: type.check def.value) defsFinal then type.merge loc defsFinal
788 else let allInvalid = filter (def: ! type.check def.value) defsFinal;
789 in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
790 else
791 # (nixos-option detects this specific error message and gives it special
792 # handling. If changed here, please change it there too.)
793 throw "The option `${showOption loc}' is used but not defined.";
794
795 isDefined = defsFinal != [];
796
797 optionalValue =
798 if isDefined then { value = mergedValue; }
799 else {};
800 };
801
802 /* Given a config set, expand mkMerge properties, and push down the
803 other properties into the children. The result is a list of
804 config sets that do not have properties at top-level. For
805 example,
806
807 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
808
809 is transformed into
810
811 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
812
813 This transform is the critical step that allows mkIf conditions
814 to refer to the full configuration without creating an infinite
815 recursion.
816 */
817 pushDownProperties = cfg:
818 if cfg._type or "" == "merge" then
819 concatMap pushDownProperties cfg.contents
820 else if cfg._type or "" == "if" then
821 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
822 else if cfg._type or "" == "override" then
823 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
824 else # FIXME: handle mkOrder?
825 [ cfg ];
826
827 /* Given a config value, expand mkMerge properties, and discharge
828 any mkIf conditions. That is, this is the place where mkIf
829 conditions are actually evaluated. The result is a list of
830 config values. For example, ‘mkIf false x’ yields ‘[]’,
831 ‘mkIf true x’ yields ‘[x]’, and
832
833 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
834
835 yields ‘[ 1 2 ]’.
836 */
837 dischargeProperties = def:
838 if def._type or "" == "merge" then
839 concatMap dischargeProperties def.contents
840 else if def._type or "" == "if" then
841 if isBool def.condition then
842 if def.condition then
843 dischargeProperties def.content
844 else
845 [ ]
846 else
847 throw "‘mkIf’ called with a non-Boolean condition"
848 else
849 [ def ];
850
851 /* Given a list of config values, process the mkOverride properties,
852 that is, return the values that have the highest (that is,
853 numerically lowest) priority, and strip the mkOverride
854 properties. For example,
855
856 [ { file = "/1"; value = mkOverride 10 "a"; }
857 { file = "/2"; value = mkOverride 20 "b"; }
858 { file = "/3"; value = "z"; }
859 { file = "/4"; value = mkOverride 10 "d"; }
860 ]
861
862 yields
863
864 [ { file = "/1"; value = "a"; }
865 { file = "/4"; value = "d"; }
866 ]
867
868 Note that "z" has the default priority 100.
869 */
870 filterOverrides = defs: (filterOverrides' defs).values;
871
872 filterOverrides' = defs:
873 let
874 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPriority;
875 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
876 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
877 in {
878 values = concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
879 inherit highestPrio;
880 };
881
882 /* Sort a list of properties. The sort priority of a property is
883 1000 by default, but can be overridden by wrapping the property
884 using mkOrder. */
885 sortProperties = defs:
886 let
887 strip = def:
888 if def.value._type or "" == "order"
889 then def // { value = def.value.content; inherit (def.value) priority; }
890 else def;
891 defs' = map strip defs;
892 compare = a: b: (a.priority or 1000) < (b.priority or 1000);
893 in sort compare defs';
894
895 # This calls substSubModules, whose entire purpose is only to ensure that
896 # option declarations in submodules have accurate position information.
897 # TODO: Merge this into mergeOptionDecls
898 fixupOptionType = loc: opt:
899 if opt.type.getSubModules or null == null
900 then opt // { type = opt.type or types.unspecified; }
901 else opt // { type = opt.type.substSubModules opt.options; options = []; };
902
903
904 /* Properties. */
905
906 mkIf = condition: content:
907 { _type = "if";
908 inherit condition content;
909 };
910
911 mkAssert = assertion: message: content:
912 mkIf
913 (if assertion then true else throw "\nFailed assertion: ${message}")
914 content;
915
916 mkMerge = contents:
917 { _type = "merge";
918 inherit contents;
919 };
920
921 mkOverride = priority: content:
922 { _type = "override";
923 inherit priority content;
924 };
925
926 mkOptionDefault = mkOverride 1500; # priority of option defaults
927 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
928 mkImageMediaOverride = mkOverride 60; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
929 mkForce = mkOverride 50;
930 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
931
932 mkFixStrictness = lib.warn "lib.mkFixStrictness has no effect and will be removed. It returns its argument unmodified, so you can just remove any calls." id;
933
934 mkOrder = priority: content:
935 { _type = "order";
936 inherit priority content;
937 };
938
939 mkBefore = mkOrder 500;
940 mkAfter = mkOrder 1500;
941
942 # The default priority for things that don't have a priority specified.
943 defaultPriority = 100;
944
945 # Convenient property used to transfer all definitions and their
946 # properties from one option to another. This property is useful for
947 # renaming options, and also for including properties from another module
948 # system, including sub-modules.
949 #
950 # { config, options, ... }:
951 #
952 # {
953 # # 'bar' might not always be defined in the current module-set.
954 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
955 #
956 # # 'barbaz' has to be defined in the current module-set.
957 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
958 # }
959 #
960 # Note, this is different than taking the value of the option and using it
961 # as a definition, as the new definition will not keep the mkOverride /
962 # mkDefault properties of the previous option.
963 #
964 mkAliasDefinitions = mkAliasAndWrapDefinitions id;
965 mkAliasAndWrapDefinitions = wrap: option:
966 mkAliasIfDef option (wrap (mkMerge option.definitions));
967
968 # Similar to mkAliasAndWrapDefinitions but copies over the priority from the
969 # option as well.
970 #
971 # If a priority is not set, it assumes a priority of defaultPriority.
972 mkAliasAndWrapDefsWithPriority = wrap: option:
973 let
974 prio = option.highestPrio or defaultPriority;
975 defsWithPrio = map (mkOverride prio) option.definitions;
976 in mkAliasIfDef option (wrap (mkMerge defsWithPrio));
977
978 mkAliasIfDef = option:
979 mkIf (isOption option && option.isDefined);
980
981 /* Compatibility. */
982 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
983
984
985 /* Return a module that causes a warning to be shown if the
986 specified option is defined. For example,
987
988 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
989
990 causes a assertion if the user defines boot.loader.grub.bootDevice.
991
992 replacementInstructions is a string that provides instructions on
993 how to achieve the same functionality without the removed option,
994 or alternatively a reasoning why the functionality is not needed.
995 replacementInstructions SHOULD be provided!
996 */
997 mkRemovedOptionModule = optionName: replacementInstructions:
998 { options, ... }:
999 { options = setAttrByPath optionName (mkOption {
1000 visible = false;
1001 apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}";
1002 });
1003 config.assertions =
1004 let opt = getAttrFromPath optionName options; in [{
1005 assertion = !opt.isDefined;
1006 message = ''
1007 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
1008 ${replacementInstructions}
1009 '';
1010 }];
1011 };
1012
1013 /* Return a module that causes a warning to be shown if the
1014 specified "from" option is defined; the defined value is however
1015 forwarded to the "to" option. This can be used to rename options
1016 while providing backward compatibility. For example,
1017
1018 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
1019
1020 forwards any definitions of boot.copyKernels to
1021 boot.loader.grub.copyKernels while printing a warning.
1022
1023 This also copies over the priority from the aliased option to the
1024 non-aliased option.
1025 */
1026 mkRenamedOptionModule = from: to: doRename {
1027 inherit from to;
1028 visible = false;
1029 warn = true;
1030 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
1031 };
1032
1033 mkRenamedOptionModuleWith = {
1034 /* Old option path as list of strings. */
1035 from,
1036 /* New option path as list of strings. */
1037 to,
1038
1039 /*
1040 Release number of the first release that contains the rename, ignoring backports.
1041 Set it to the upcoming release, matching the nixpkgs/.version file.
1042 */
1043 sinceRelease,
1044
1045 }: doRename {
1046 inherit from to;
1047 visible = false;
1048 warn = lib.isInOldestRelease sinceRelease;
1049 use = lib.warnIf (lib.isInOldestRelease sinceRelease)
1050 "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
1051 };
1052
1053 /* Return a module that causes a warning to be shown if any of the "from"
1054 option is defined; the defined values can be used in the "mergeFn" to set
1055 the "to" value.
1056 This function can be used to merge multiple options into one that has a
1057 different type.
1058
1059 "mergeFn" takes the module "config" as a parameter and must return a value
1060 of "to" option type.
1061
1062 mkMergedOptionModule
1063 [ [ "a" "b" "c" ]
1064 [ "d" "e" "f" ] ]
1065 [ "x" "y" "z" ]
1066 (config:
1067 let value = p: getAttrFromPath p config;
1068 in
1069 if (value [ "a" "b" "c" ]) == true then "foo"
1070 else if (value [ "d" "e" "f" ]) == true then "bar"
1071 else "baz")
1072
1073 - options.a.b.c is a removed boolean option
1074 - options.d.e.f is a removed boolean option
1075 - options.x.y.z is a new str option that combines a.b.c and d.e.f
1076 functionality
1077
1078 This show a warning if any a.b.c or d.e.f is set, and set the value of
1079 x.y.z to the result of the merge function
1080 */
1081 mkMergedOptionModule = from: to: mergeFn:
1082 { config, options, ... }:
1083 {
1084 options = foldl' recursiveUpdate {} (map (path: setAttrByPath path (mkOption {
1085 visible = false;
1086 # To use the value in mergeFn without triggering errors
1087 default = "_mkMergedOptionModule";
1088 })) from);
1089
1090 config = {
1091 warnings = filter (x: x != "") (map (f:
1092 let val = getAttrFromPath f config;
1093 opt = getAttrFromPath f options;
1094 in
1095 optionalString
1096 (val != "_mkMergedOptionModule")
1097 "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly."
1098 ) from);
1099 } // setAttrByPath to (mkMerge
1100 (optional
1101 (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
1102 (mergeFn config)));
1103 };
1104
1105 /* Single "from" version of mkMergedOptionModule.
1106 Return a module that causes a warning to be shown if the "from" option is
1107 defined; the defined value can be used in the "mergeFn" to set the "to"
1108 value.
1109 This function can be used to change an option into another that has a
1110 different type.
1111
1112 "mergeFn" takes the module "config" as a parameter and must return a value of
1113 "to" option type.
1114
1115 mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ]
1116 (config:
1117 let value = getAttrFromPath [ "a" "b" "c" ] config;
1118 in
1119 if value > 100 then "high"
1120 else "normal")
1121
1122 - options.a.b.c is a removed int option
1123 - options.x.y.z is a new str option that supersedes a.b.c
1124
1125 This show a warning if a.b.c is set, and set the value of x.y.z to the
1126 result of the change function
1127 */
1128 mkChangedOptionModule = from: to: changeFn:
1129 mkMergedOptionModule [ from ] to changeFn;
1130
1131 /* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */
1132 mkAliasOptionModule = from: to: doRename {
1133 inherit from to;
1134 visible = true;
1135 warn = false;
1136 use = id;
1137 };
1138
1139 /* mkDerivedConfig : Option a -> (a -> Definition b) -> Definition b
1140
1141 Create config definitions with the same priority as the definition of another option.
1142 This should be used for option definitions where one option sets the value of another as a convenience.
1143 For instance a config file could be set with a `text` or `source` option, where text translates to a `source`
1144 value using `mkDerivedConfig options.text (pkgs.writeText "filename.conf")`.
1145
1146 It takes care of setting the right priority using `mkOverride`.
1147 */
1148 # TODO: make the module system error message include information about `opt` in
1149 # error messages about conflicts. E.g. introduce a variation of `mkOverride` which
1150 # adds extra location context to the definition object. This will allow context to be added
1151 # to all messages that report option locations "this value was derived from <full option name>
1152 # which was defined in <locations>". It can provide a trace of options that contributed
1153 # to definitions.
1154 mkDerivedConfig = opt: f:
1155 mkOverride
1156 (opt.highestPrio or defaultPriority)
1157 (f opt.value);
1158
1159 doRename = { from, to, visible, warn, use, withPriority ? true }:
1160 { config, options, ... }:
1161 let
1162 fromOpt = getAttrFromPath from options;
1163 toOf = attrByPath to
1164 (abort "Renaming error: option `${showOption to}' does not exist.");
1165 toType = let opt = attrByPath to {} options; in opt.type or (types.submodule {});
1166 in
1167 {
1168 options = setAttrByPath from (mkOption {
1169 inherit visible;
1170 description = "Alias of <option>${showOption to}</option>.";
1171 apply = x: use (toOf config);
1172 } // optionalAttrs (toType != null) {
1173 type = toType;
1174 });
1175 config = mkMerge [
1176 {
1177 warnings = optional (warn && fromOpt.isDefined)
1178 "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
1179 }
1180 (if withPriority
1181 then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt
1182 else mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt)
1183 ];
1184 };
1185
1186 /* Use this function to import a JSON file as NixOS configuration.
1187
1188 modules.importJSON :: path -> attrs
1189 */
1190 importJSON = file: {
1191 _file = file;
1192 config = lib.importJSON file;
1193 };
1194
1195 /* Use this function to import a TOML file as NixOS configuration.
1196
1197 modules.importTOML :: path -> attrs
1198 */
1199 importTOML = file: {
1200 _file = file;
1201 config = lib.importTOML file;
1202 };
1203}