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