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