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