1{ lib }:
2
3let
4 inherit (lib)
5 addErrorContext
6 all
7 any
8 attrByPath
9 attrNames
10 catAttrs
11 concatLists
12 concatMap
13 concatStringsSep
14 elem
15 filter
16 foldl'
17 functionArgs
18 getAttrFromPath
19 genericClosure
20 head
21 id
22 imap1
23 isAttrs
24 isBool
25 isFunction
26 oldestSupportedReleaseIsAtLeast
27 isList
28 isString
29 length
30 mapAttrs
31 mapAttrsToList
32 mapAttrsRecursiveCond
33 min
34 optional
35 optionalAttrs
36 optionalString
37 recursiveUpdate
38 reverseList
39 sort
40 seq
41 setAttrByPath
42 substring
43 throwIfNot
44 trace
45 typeOf
46 types
47 unsafeGetAttrPos
48 warn
49 warnIf
50 zipAttrs
51 zipAttrsWith
52 ;
53 inherit (lib.options)
54 isOption
55 mkOption
56 showDefs
57 showFiles
58 showOption
59 unknownModule
60 ;
61 inherit (lib.strings)
62 isConvertibleWithToString
63 ;
64
65 showDeclPrefix =
66 loc: decl: prefix:
67 " - option(s) with prefix `${showOption (loc ++ [ prefix ])}' in module `${decl._file}'";
68 showRawDecls =
69 loc: decls:
70 concatStringsSep "\n" (
71 sort (a: b: a < b) (concatMap (decl: map (showDeclPrefix loc decl) (attrNames decl.options)) decls)
72 );
73
74 /**
75 See https://nixos.org/manual/nixpkgs/unstable/#module-system-lib-evalModules
76 or file://./../doc/module-system/module-system.chapter.md
77
78 !!! Please think twice before adding to this argument list! The more
79 that is specified here instead of in the modules themselves the harder
80 it is to transparently move a set of modules to be a submodule of another
81 config (as the proper arguments need to be replicated at each call to
82 evalModules) and the less declarative the module set is.
83 */
84 evalModules =
85 evalModulesArgs@{
86 modules,
87 prefix ? [ ],
88 # This should only be used for special arguments that need to be evaluated
89 # when resolving module structure (like in imports). For everything else,
90 # there's _module.args. If specialArgs.modulesPath is defined it will be
91 # used as the base path for disabledModules.
92 specialArgs ? { },
93 # `class`:
94 # A nominal type for modules. When set and non-null, this adds a check to
95 # make sure that only compatible modules are imported.
96 class ? null,
97 # This would be remove in the future, Prefer _module.args option instead.
98 args ? { },
99 # This would be remove in the future, Prefer _module.check option instead.
100 check ? true,
101 }:
102 let
103 withWarnings =
104 x:
105 warnIf (evalModulesArgs ? args)
106 "The args argument to evalModules is deprecated. Please set config._module.args instead."
107 warnIf
108 (evalModulesArgs ? check)
109 "The check argument to evalModules is deprecated. Please set config._module.check instead."
110 x;
111
112 legacyModules =
113 optional (evalModulesArgs ? args) {
114 config = {
115 _module.args = args;
116 };
117 }
118 ++ optional (evalModulesArgs ? check) {
119 config = {
120 _module.check = mkDefault check;
121 };
122 };
123 regularModules = modules ++ legacyModules;
124
125 # This internal module declare internal options under the `_module'
126 # attribute. These options are fragile, as they are used by the
127 # module system to change the interpretation of modules.
128 #
129 # When extended with extendModules or moduleType, a fresh instance of
130 # this module is used, to avoid conflicts and allow chaining of
131 # extendModules.
132 internalModule = rec {
133 _file = "lib/modules.nix";
134
135 key = _file;
136
137 options = {
138 _module.args = mkOption {
139 # Because things like `mkIf` are entirely useless for
140 # `_module.args` (because there's no way modules can check which
141 # arguments were passed), we'll use `lazyAttrsOf` which drops
142 # support for that, in turn it's lazy in its values. This means e.g.
143 # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
144 # start a download when `pkgs` wasn't evaluated.
145 type = types.lazyAttrsOf types.raw;
146 # Only render documentation once at the root of the option tree,
147 # not for all individual submodules.
148 # Allow merging option decls to make this internal regardless.
149 ${
150 if prefix == [ ] then
151 null # unset => visible
152 else
153 "internal"
154 } =
155 true;
156 # TODO: Change the type of this option to a submodule with a
157 # freeformType, so that individual arguments can be documented
158 # separately
159 description = ''
160 Additional arguments passed to each module in addition to ones
161 like `lib`, `config`,
162 and `pkgs`, `modulesPath`.
163
164 This option is also available to all submodules. Submodules do not
165 inherit args from their parent module, nor do they provide args to
166 their parent module or sibling submodules. The sole exception to
167 this is the argument `name` which is provided by
168 parent modules to a submodule and contains the attribute name
169 the submodule is bound to, or a unique generated name if it is
170 not bound to an attribute.
171
172 Some arguments are already passed by default, of which the
173 following *cannot* be changed with this option:
174 - {var}`lib`: The nixpkgs library.
175 - {var}`config`: The results of all options after merging the values from all modules together.
176 - {var}`options`: The options declared in all modules.
177 - {var}`specialArgs`: The `specialArgs` argument passed to `evalModules`.
178 - All attributes of {var}`specialArgs`
179
180 Whereas option values can generally depend on other option values
181 thanks to laziness, this does not apply to `imports`, which
182 must be computed statically before anything else.
183
184 For this reason, callers of the module system can provide `specialArgs`
185 which are available during import resolution.
186
187 For NixOS, `specialArgs` includes
188 {var}`modulesPath`, which allows you to import
189 extra modules from the nixpkgs package tree without having to
190 somehow make the module aware of the location of the
191 `nixpkgs` or NixOS directories.
192 ```
193 { modulesPath, ... }: {
194 imports = [
195 (modulesPath + "/profiles/minimal.nix")
196 ];
197 }
198 ```
199
200 For NixOS, the default value for this option includes at least this argument:
201 - {var}`pkgs`: The nixpkgs package set according to
202 the {option}`nixpkgs.pkgs` option.
203 '';
204 };
205
206 _module.check = mkOption {
207 type = types.bool;
208 internal = true;
209 default = true;
210 description = "Whether to check whether all option definitions have matching declarations.";
211 };
212
213 _module.freeformType = mkOption {
214 type = types.nullOr types.optionType;
215 internal = true;
216 default = null;
217 description = ''
218 If set, merge all definitions that don't have an associated option
219 together using this type. The result then gets combined with the
220 values of all declared options to produce the final `
221 config` value.
222
223 If this is `null`, definitions without an option
224 will throw an error unless {option}`_module.check` is
225 turned off.
226 '';
227 };
228
229 _module.specialArgs = mkOption {
230 readOnly = true;
231 internal = true;
232 description = ''
233 Externally provided module arguments that can't be modified from
234 within a configuration, but can be used in module imports.
235 '';
236 };
237 };
238
239 config = {
240 _module.args = {
241 inherit extendModules;
242 moduleType = type;
243 };
244 _module.specialArgs = specialArgs;
245 };
246 };
247
248 # This function takes an empty attrset as an argument.
249 # It could theoretically be replaced with its body,
250 # but such a binding is avoided to allow for earlier grabage collection.
251 doCollect =
252 { }:
253 collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) (
254 {
255 inherit
256 lib
257 options
258 specialArgs
259 ;
260 _class = class;
261 _prefix = prefix;
262 config = addErrorContext "if you get an infinite recursion here, you probably reference `config` in `imports`. If you are trying to achieve a conditional import behavior dependent on `config`, consider importing unconditionally, and using `mkEnableOption` and `mkIf` to control its effect." config;
263 }
264 // specialArgs
265 );
266
267 merged = mergeModules prefix (reverseList (doCollect { }).modules);
268
269 options = merged.matchedOptions;
270
271 config =
272 let
273
274 # For definitions that have an associated option
275 declaredConfig = mapAttrsRecursiveCond (v: !isOption v) (_: v: v.value) options;
276
277 # If freeformType is set, this is for definitions that don't have an associated option
278 freeformConfig =
279 let
280 defs = map (def: {
281 file = def.file;
282 value = setAttrByPath def.prefix def.value;
283 }) merged.unmatchedDefns;
284 in
285 if defs == [ ] then { } else declaredConfig._module.freeformType.merge prefix defs;
286
287 in
288 if declaredConfig._module.freeformType == null then
289 declaredConfig
290 # Because all definitions that had an associated option ended in
291 # declaredConfig, freeformConfig can only contain the non-option
292 # paths, meaning recursiveUpdate will never override any value
293 else
294 recursiveUpdate freeformConfig declaredConfig;
295
296 checkUnmatched =
297 if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
298 let
299 firstDef = head merged.unmatchedDefns;
300 baseMsg =
301 let
302 optText = showOption (prefix ++ firstDef.prefix);
303 defText =
304 addErrorContext
305 "while evaluating the error message for definitions for `${optText}', which is an option that does not exist"
306 (addErrorContext "while evaluating a definition from `${firstDef.file}'" (showDefs [ firstDef ]));
307 in
308 "The option `${optText}' does not exist. Definition values:${defText}";
309 in
310 if
311 attrNames options == [ "_module" ]
312 # No options were declared at all (`_module` is built in)
313 # but we do have unmatched definitions, and no freeformType (earlier conditions)
314 then
315 let
316 optionName = showOption prefix;
317 in
318 if optionName == "" then
319 throw ''
320 ${baseMsg}
321
322 It seems as if you're trying to declare an option by placing it into `config' rather than `options'!
323 ''
324 else
325 throw ''
326 ${baseMsg}
327
328 However there are no options defined in `${showOption prefix}'. Are you sure you've
329 declared your options properly? This can happen if you e.g. declared your options in `types.submodule'
330 under `config' rather than `options'.
331 ''
332 else
333 throw baseMsg
334 else
335 null;
336
337 checked = seq checkUnmatched;
338
339 extendModules =
340 extendArgs@{
341 modules ? [ ],
342 specialArgs ? { },
343 prefix ? [ ],
344 }:
345 evalModules (
346 evalModulesArgs
347 // {
348 modules = regularModules ++ modules;
349 specialArgs = evalModulesArgs.specialArgs or { } // specialArgs;
350 prefix = extendArgs.prefix or evalModulesArgs.prefix or [ ];
351 }
352 );
353
354 type = types.submoduleWith {
355 inherit modules specialArgs class;
356 };
357
358 result = withWarnings {
359 _type = "configuration";
360 options = checked options;
361 config = checked (removeAttrs config [ "_module" ]);
362 _module = checked (config._module);
363 inherit (doCollect { }) graph;
364 inherit extendModules type class;
365 };
366 in
367 result;
368
369 # collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> ModulesTree
370 #
371 # Collects all modules recursively through `import` statements, filtering out
372 # all modules in disabledModules.
373 collectModules =
374 class:
375 let
376
377 # Like unifyModuleSyntax, but also imports paths and calls functions if necessary
378 loadModule =
379 args: fallbackFile: fallbackKey: m:
380 if isFunction m then
381 unifyModuleSyntax fallbackFile fallbackKey (applyModuleArgs fallbackKey m args)
382 else if isAttrs m then
383 if m._type or "module" == "module" then
384 unifyModuleSyntax fallbackFile fallbackKey m
385 else if m._type == "if" || m._type == "override" then
386 loadModule args fallbackFile fallbackKey { config = m; }
387 else
388 throw (
389 messages.not_a_module {
390 inherit fallbackFile;
391 value = m;
392 _type = m._type;
393 expectedClass = class;
394 prefix = args._prefix;
395 }
396 )
397 else if isList m then
398 let
399 defs = [
400 {
401 file = fallbackFile;
402 value = m;
403 }
404 ];
405 in
406 throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
407 else
408 unifyModuleSyntax (toString m) (toString m) (
409 applyModuleArgsIfFunction (toString m) (import m) args
410 );
411
412 checkModule =
413 if class != null then
414 m:
415 if m._class == null || m._class == class then
416 m
417 else
418 throw ''
419 The module `${m._file or m.key}` (class: ${lib.strings.escapeNixString m._class}) cannot be imported into a module evaluation that expects class ${lib.strings.escapeNixString class}.
420
421 Help:
422 - Ensure that you are importing the correct module.
423 - Verify that the module's `_class`, ${lib.strings.escapeNixString m._class} matches the expected `class` ${lib.strings.escapeNixString class}.
424 - If you are using a custom class, make sure it is correctly defined and used consistently across your modules.
425 ''
426 else
427 m: m;
428
429 # isDisabled :: String -> [ { disabled, file } ] -> StructuredModule -> bool
430 #
431 # Figures out whether a `StructuredModule` is disabled.
432 isDisabled =
433 modulesPath: disabledList:
434 let
435 moduleKey =
436 file: m:
437 if isString m then
438 if substring 0 1 m == "/" then m else toString modulesPath + "/" + m
439
440 else if isConvertibleWithToString m then
441 if m ? key && m.key != toString m then
442 throw "Module `${file}` contains a disabledModules item that is an attribute set that can be converted to a string (${toString m}) but also has a `.key` attribute (${m.key}) with a different value. This makes it ambiguous which module should be disabled."
443 else
444 toString m
445
446 else if m ? key then
447 m.key
448
449 else if isAttrs m then
450 throw "Module `${file}` contains a disabledModules item that is an attribute set, presumably a module, that does not have a `key` attribute. This means that the module system doesn't have any means to identify the module that should be disabled. Make sure that you've put the correct value in disabledModules: a string path relative to modulesPath, a path value, or an attribute set with a `key` attribute."
451 else
452 throw "Each disabledModules item must be a path, string, or a attribute set with a key attribute, or a value supported by toString. However, one of the disabledModules items in `${toString file}` is none of that, but is of type ${typeOf m}.";
453
454 disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabledList;
455 in
456 structuredModule: elem structuredModule.key disabledKeys;
457
458 /**
459 Collects all modules recursively into a `[ StructuredModule ]` and a list of disabled modules:
460
461 {
462 disabled = [ <list of disabled modules> ];
463 # All modules of the main module list
464 modules = [
465 {
466 key = <key1>;
467 module = <module for key1>;
468 # All modules imported by the module for key1
469 modules = [
470 {
471 key = <key1-1>;
472 module = <module for key1-1>;
473 # All modules imported by the module for key1-1
474 modules = [ ... ];
475 }
476 ...
477 ];
478 }
479 ...
480 ];
481 }
482 */
483 collectStructuredModules =
484 let
485 collectResults = modules: {
486 disabled = concatLists (catAttrs "disabled" modules);
487 inherit modules;
488 };
489 in
490 parentFile: parentKey: initialModules: args:
491 collectResults (
492 imap1 (
493 n: x:
494 let
495 module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
496 collectedImports = collectStructuredModules module._file module.key module.imports args;
497 in
498 {
499 key = module.key;
500 module = module;
501 modules = collectedImports.modules;
502 disabled =
503 (
504 if module.disabledModules != [ ] then
505 [
506 {
507 file = module._file;
508 disabled = module.disabledModules;
509 }
510 ]
511 else
512 [ ]
513 )
514 ++ collectedImports.disabled;
515 }
516 ) initialModules
517 );
518
519 # filterModules :: String -> { disabled, modules } -> [ Module ]
520 #
521 # Filters a structure as emitted by collectStructuredModules by removing all disabled
522 # modules recursively. It returns the final list of unique-by-key modules
523 filterModules =
524 modulesPath:
525 { disabled, modules }:
526 let
527 keyFilter = filter (attrs: !isDisabled modulesPath disabled attrs);
528 in
529 map (attrs: attrs.module) (genericClosure {
530 startSet = keyFilter modules;
531 operator = attrs: keyFilter attrs.modules;
532 });
533
534 toGraph =
535 modulesPath:
536 { disabled, modules }:
537 let
538 isDisabledModule = isDisabled modulesPath disabled;
539
540 toModuleGraph = structuredModule: {
541 disabled = isDisabledModule structuredModule;
542 inherit (structuredModule) key;
543 file = structuredModule.module._file;
544 imports = map toModuleGraph structuredModule.modules;
545 };
546 in
547 map toModuleGraph (filter (x: x.key != "lib/modules.nix") modules);
548 in
549 modulesPath: initialModules: args: {
550 modules = filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
551 graph = toGraph modulesPath (collectStructuredModules unknownModule "" initialModules args);
552 };
553
554 /**
555 Wrap a module with a default location for reporting errors.
556
557 # Inputs
558
559 `file`
560
561 : 1\. Function argument
562
563 `m`
564
565 : 2\. Function argument
566 */
567 setDefaultModuleLocation = file: m: {
568 _file = file;
569 imports = [ m ];
570 };
571
572 /**
573 Massage a module into canonical form, that is, a set consisting
574 of ‘options’, ‘config’ and ‘imports’ attributes.
575
576 # Inputs
577
578 `file`
579
580 : 1\. Function argument
581
582 `key`
583
584 : 2\. Function argument
585
586 `m`
587
588 : 3\. Function argument
589 */
590 unifyModuleSyntax =
591 file: key: m:
592 let
593 addMeta =
594 config:
595 if m ? meta then
596 mkMerge [
597 config
598 { meta = m.meta; }
599 ]
600 else
601 config;
602 addFreeformType =
603 config:
604 if m ? freeformType then
605 mkMerge [
606 config
607 { _module.freeformType = m.freeformType; }
608 ]
609 else
610 config;
611 in
612 if m ? config || m ? options then
613 let
614 badAttrs = removeAttrs m [
615 "_class"
616 "_file"
617 "key"
618 "disabledModules"
619 "imports"
620 "options"
621 "config"
622 "meta"
623 "freeformType"
624 ];
625 in
626 if badAttrs != { } then
627 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."
628 else
629 {
630 _file = toString m._file or file;
631 _class = m._class or null;
632 key = toString m.key or key;
633 disabledModules = m.disabledModules or [ ];
634 imports = m.imports or [ ];
635 options = m.options or { };
636 config = addFreeformType (addMeta (m.config or { }));
637 }
638 else
639 # shorthand syntax
640 throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module." {
641 _file = toString m._file or file;
642 _class = m._class or null;
643 key = toString m.key or key;
644 disabledModules = m.disabledModules or [ ];
645 imports = m.require or [ ] ++ m.imports or [ ];
646 options = { };
647 config = addFreeformType (
648 removeAttrs m [
649 "_class"
650 "_file"
651 "key"
652 "disabledModules"
653 "require"
654 "imports"
655 "freeformType"
656 ]
657 );
658 };
659
660 applyModuleArgsIfFunction =
661 key: f: args@{ config, ... }: if isFunction f then applyModuleArgs key f args else f;
662
663 applyModuleArgs =
664 key: f:
665 args@{ config, ... }:
666 let
667 # Module arguments are resolved in a strict manner when attribute set
668 # deconstruction is used. As the arguments are now defined with the
669 # config._module.args option, the strictness used on the attribute
670 # set argument would cause an infinite loop, if the result of the
671 # option is given as argument.
672 #
673 # To work-around the strictness issue on the deconstruction of the
674 # attributes set argument, we create a new attribute set which is
675 # constructed to satisfy the expected set of attributes. Thus calling
676 # a module will resolve strictly the attributes used as argument but
677 # not their values. The values are forwarding the result of the
678 # evaluation of the option.
679 context = name: ''while evaluating the module argument `${name}' in "${key}":'';
680 extraArgs = mapAttrs (
681 name: _:
682 addErrorContext (context name) (
683 args.${name} or (addErrorContext
684 "noting that argument `${name}` is not externally provided, so querying `_module.args` instead, requiring `config`"
685 config._module.args.${name}
686 )
687 )
688 ) (functionArgs f);
689
690 # Note: we append in the opposite order such that we can add an error
691 # context on the explicit arguments of "args" too. This update
692 # operator is used to make the "args@{ ... }: with args.lib;" notation
693 # works.
694 in
695 f (args // extraArgs);
696
697 /**
698 Merge a list of modules. This will recurse over the option
699 declarations in all modules, combining them into a single set.
700 At the same time, for each option declaration, it will merge the
701 corresponding option definitions in all machines, returning them
702 in the ‘value’ attribute of each option.
703
704 This returns a set like
705 {
706 # A recursive set of options along with their final values
707 matchedOptions = {
708 foo = { _type = "option"; value = "option value of foo"; ... };
709 bar.baz = { _type = "option"; value = "option value of bar.baz"; ... };
710 ...
711 };
712 # A list of definitions that weren't matched by any option
713 unmatchedDefns = [
714 { file = "file.nix"; prefix = [ "qux" ]; value = "qux"; }
715 ...
716 ];
717 }
718
719 # Inputs
720
721 `prefix`
722
723 : 1\. Function argument
724
725 `modules`
726
727 : 2\. Function argument
728 */
729 mergeModules =
730 prefix: modules:
731 mergeModules' prefix modules (
732 concatMap (
733 m:
734 map (config: {
735 file = m._file;
736 inherit config;
737 }) (pushDownProperties m.config)
738 ) modules
739 );
740
741 mergeModules' =
742 prefix: modules: configs:
743 let
744 # an attrset 'name' => list of submodules that declare ‘name’.
745 declsByName = zipAttrsWith (n: v: v) (
746 map (
747 module:
748 let
749 subtree = module.options;
750 in
751 if !(isAttrs subtree) then
752 throw ''
753 An option declaration for `${concatStringsSep "." prefix}' has type
754 `${typeOf subtree}' rather than an attribute set.
755 Did you mean to define this outside of `options'?
756 ''
757 else
758 mapAttrs (n: option: {
759 inherit (module) _file;
760 pos = unsafeGetAttrPos n subtree;
761 options = option;
762 }) subtree
763 ) modules
764 );
765
766 # The root of any module definition must be an attrset.
767 checkedConfigs =
768 assert all (
769 c:
770 # TODO: I have my doubts that this error would occur when option definitions are not matched.
771 # The implementation of this check used to be tied to a superficially similar check for
772 # options, so maybe that's why this is here.
773 isAttrs c.config
774 || throw ''
775 In module `${c.file}', you're trying to define a value of type `${typeOf c.config}'
776 rather than an attribute set for the option
777 `${concatStringsSep "." prefix}'!
778
779 This usually happens if `${concatStringsSep "." prefix}' has option
780 definitions inside that are not matched. Please check how to properly define
781 this option by e.g. referring to `man 5 configuration.nix'!
782 ''
783 ) configs;
784 configs;
785
786 # an attrset 'name' => list of submodules that define ‘name’.
787 pushedDownDefinitionsByName = zipAttrsWith (n: concatLists) (
788 map (
789 module:
790 mapAttrs (
791 n: value:
792 map (config: {
793 inherit (module) file;
794 inherit config;
795 }) (pushDownProperties value)
796 ) module.config
797 ) checkedConfigs
798 );
799 # extract the definitions for each loc
800 rawDefinitionsByName = zipAttrsWith (n: v: v) (
801 map (
802 module:
803 mapAttrs (n: value: {
804 inherit (module) file;
805 inherit value;
806 }) module.config
807 ) checkedConfigs
808 );
809
810 # Convert an option tree decl to a submodule option decl
811 optionTreeToOption =
812 decl:
813 if isOption decl.options then
814 decl
815 else
816 decl
817 // {
818 options = mkOption {
819 type = types.submoduleWith {
820 modules = [ { options = decl.options; } ];
821 # `null` is not intended for use by modules. It is an internal
822 # value that means "whatever the user has declared elsewhere".
823 # This might become obsolete with https://github.com/NixOS/nixpkgs/issues/162398
824 shorthandOnlyDefinesConfig = null;
825 };
826 };
827 };
828
829 resultsByName = mapAttrs (
830 name: decls:
831 # We're descending into attribute ‘name’.
832 let
833 loc = prefix ++ [ name ];
834 defns = pushedDownDefinitionsByName.${name} or [ ];
835 defns' = rawDefinitionsByName.${name} or [ ];
836 optionDecls = filter (
837 m:
838 m.options ? _type
839 && (m.options._type == "option" || throwDeclarationTypeError loc m.options._type m._file)
840 ) decls;
841 in
842 if length optionDecls == length decls then
843 let
844 opt = fixupOptionType loc (mergeOptionDecls loc decls);
845 in
846 {
847 matchedOptions = evalOptionValue loc opt defns';
848 unmatchedDefns = [ ];
849 }
850 else if optionDecls != [ ] then
851 if
852 all (x: x.options.type.name or null == "submodule") optionDecls
853 # Raw options can only be merged into submodules. Merging into
854 # attrsets might be nice, but ambiguous. Suppose we have
855 # attrset as a `attrsOf submodule`. User declares option
856 # attrset.foo.bar, this could mean:
857 # a. option `bar` is only available in `attrset.foo`
858 # b. option `foo.bar` is available in all `attrset.*`
859 # c. reject and require "<name>" as a reminder that it behaves like (b).
860 # d. magically combine (a) and (c).
861 # All of the above are merely syntax sugar though.
862 then
863 let
864 opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls));
865 in
866 {
867 matchedOptions = evalOptionValue loc opt defns';
868 unmatchedDefns = [ ];
869 }
870 else
871 let
872 nonOptions = filter (m: !isOption m.options) decls;
873 in
874 throw "The option `${showOption loc}' in module `${(head optionDecls)._file}' would be a parent of the following options, but its type `${
875 (head optionDecls).options.type.description or "<no description>"
876 }' does not support nested options.\n${showRawDecls loc nonOptions}"
877 else
878 mergeModules' loc decls defns
879 ) declsByName;
880
881 matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName;
882
883 # an attrset 'name' => list of unmatched definitions for 'name'
884 unmatchedDefnsByName =
885 # Propagate all unmatched definitions from nested option sets
886 mapAttrs (n: v: v.unmatchedDefns) resultsByName
887 # Plus the definitions for the current prefix that don't have a matching option
888 // removeAttrs rawDefinitionsByName (attrNames matchedOptions);
889 in
890 {
891 inherit matchedOptions;
892
893 # Transforms unmatchedDefnsByName into a list of definitions
894 unmatchedDefns =
895 if configs == [ ] then
896 # When no config values exist, there can be no unmatched config, so
897 # we short circuit and avoid evaluating more _options_ than necessary.
898 [ ]
899 else
900 concatLists (
901 mapAttrsToList (
902 name: defs:
903 map (
904 def:
905 def
906 // {
907 # Set this so we know when the definition first left unmatched territory
908 prefix = [ name ] ++ (def.prefix or [ ]);
909 }
910 ) defs
911 ) unmatchedDefnsByName
912 );
913 };
914
915 throwDeclarationTypeError =
916 loc: actualTag: file:
917 let
918 name = lib.strings.escapeNixIdentifier (lib.lists.last loc);
919 path = showOption loc;
920 depth = length loc;
921
922 paragraphs = [
923 "In module ${file}: expected an option declaration at option path `${path}` but got an attribute set with type ${actualTag}"
924 ]
925 ++ optional (actualTag == "option-type") ''
926 When declaring an option, you must wrap the type in a `mkOption` call. It should look somewhat like:
927 ${comment}
928 ${name} = lib.mkOption {
929 description = ...;
930 type = <the type you wrote for ${name}>;
931 ...
932 };
933 '';
934
935 # Ideally we'd know the exact syntax they used, but short of that,
936 # we can only reliably repeat the last. However, we repeat the
937 # full path in a non-misleading way here, in case they overlook
938 # the start of the message. Examples attract attention.
939 comment = optionalString (depth > 1) "\n # ${showOption loc}";
940 in
941 throw (concatStringsSep "\n\n" paragraphs);
942
943 /**
944 Merge multiple option declarations into a single declaration. In
945 general, there should be only one declaration of each option.
946 The exception is the ‘options’ attribute, which specifies
947 sub-options. These can be specified multiple times to allow one
948 module to add sub-options to an option declared somewhere else
949 (e.g. multiple modules define sub-options for ‘fileSystems’).
950
951 'loc' is the list of attribute names where the option is located.
952
953 'opts' is a list of modules. Each module has an options attribute which
954 correspond to the definition of 'loc' in 'opt.file'.
955
956 # Inputs
957
958 `loc`
959
960 : 1\. Function argument
961
962 `opts`
963
964 : 2\. Function argument
965 */
966 mergeOptionDecls =
967 loc: opts:
968 foldl'
969 (
970 res: opt:
971 let
972 t = res.type;
973 t' = opt.options.type;
974 mergedType = t.typeMerge t'.functor;
975 typesMergeable = mergedType != null;
976
977 # TODO: Remove this when all downstream reliances of internals: 'functor.wrapped' are sufficiently migrated.
978 # A function that adds the deprecated wrapped message to a type.
979 addDeprecatedWrapped =
980 t:
981 t
982 // {
983 functor = t.functor // {
984 wrapped = t.functor.wrappedDeprecationMessage {
985 inherit loc;
986 };
987 };
988 };
989
990 typeSet =
991 if opt.options ? type then
992 if res ? type then
993 if typesMergeable then
994 {
995 type =
996 if mergedType ? functor.wrappedDeprecationMessage then
997 addDeprecatedWrapped mergedType
998 else
999 mergedType;
1000 }
1001 else
1002 # Keep in sync with the same error below!
1003 throw
1004 "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
1005 else if opt.options.type ? functor.wrappedDeprecationMessage then
1006 { type = addDeprecatedWrapped opt.options.type; }
1007 else
1008 { }
1009 else
1010 { };
1011
1012 bothHave = k: opt.options ? ${k} && res ? ${k};
1013 in
1014 if bothHave "default" || bothHave "example" || bothHave "description" || bothHave "apply" then
1015 # Keep in sync with the same error above!
1016 throw
1017 "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
1018 else
1019 let
1020 getSubModules = opt.options.type.getSubModules or null;
1021 submodules =
1022 if getSubModules != null then
1023 map (setDefaultModuleLocation opt._file) getSubModules ++ res.options
1024 else
1025 res.options;
1026 in
1027 opt.options
1028 // res
1029 // {
1030 declarations = res.declarations ++ [ opt._file ];
1031 # In the case of modules that are generated dynamically, we won't
1032 # have exact declaration lines; fall back to just the file being
1033 # evaluated.
1034 declarationPositions =
1035 res.declarationPositions
1036 ++ (
1037 if opt.pos != null then
1038 [ opt.pos ]
1039 else
1040 [
1041 {
1042 file = opt._file;
1043 line = null;
1044 column = null;
1045 }
1046 ]
1047 );
1048 options = submodules;
1049 }
1050 // typeSet
1051 )
1052 {
1053 inherit loc;
1054 declarations = [ ];
1055 declarationPositions = [ ];
1056 options = [ ];
1057 }
1058 opts;
1059
1060 /**
1061 Merge all the definitions of an option to produce the final
1062 config value.
1063
1064 # Inputs
1065
1066 `loc`
1067
1068 : 1\. Function argument
1069
1070 `opt`
1071
1072 : 2\. Function argument
1073
1074 `defs`
1075
1076 : 3\. Function argument
1077 */
1078 evalOptionValue =
1079 loc: opt: defs:
1080 let
1081 # Add in the default value for this option, if any.
1082 defs' =
1083 (optional (opt ? default) {
1084 file = head opt.declarations;
1085 value = mkOptionDefault opt.default;
1086 })
1087 ++ defs;
1088
1089 # Handle properties, check types, and merge everything together.
1090 res =
1091 if opt.readOnly or false && length defs' > 1 then
1092 let
1093 # For a better error message, evaluate all readOnly definitions as
1094 # if they were the only definition.
1095 separateDefs = map (
1096 def:
1097 def
1098 // {
1099 value = (mergeDefinitions loc opt.type [ def ]).mergedValue;
1100 }
1101 ) defs';
1102 in
1103 throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}"
1104 else
1105 mergeDefinitions loc opt.type defs';
1106
1107 # Apply the 'apply' function to the merged value. This allows options to
1108 # yield a value computed from the definitions
1109 value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
1110
1111 warnDeprecation =
1112 warnIf (opt.type.deprecationMessage != null)
1113 "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}";
1114
1115 in
1116 warnDeprecation opt
1117 // {
1118 value = addErrorContext "while evaluating the option `${showOption loc}':" value;
1119 inherit (res.defsFinal') highestPrio;
1120 definitions = map (def: def.value) res.defsFinal;
1121 files = map (def: def.file) res.defsFinal;
1122 definitionsWithLocations = res.defsFinal;
1123 inherit (res) isDefined;
1124 inherit (res.checkedAndMerged) valueMeta;
1125 # This allows options to be correctly displayed using `${options.path.to.it}`
1126 __toString = _: showOption loc;
1127 };
1128
1129 # Merge definitions of a value of a given type.
1130 mergeDefinitions = loc: type: defs: rec {
1131 defsFinal' =
1132 let
1133 # Process mkMerge and mkIf properties.
1134 defsNormalized = concatMap (
1135 m:
1136 map (
1137 value:
1138 if value._type or null == "definition" then
1139 value
1140 else
1141 {
1142 inherit (m) file;
1143 inherit value;
1144 }
1145 ) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
1146 ) defs;
1147
1148 # Process mkOverride properties.
1149 defsFiltered = filterOverrides' defsNormalized;
1150
1151 # Sort mkOrder properties.
1152 defsSorted =
1153 # Avoid sorting if we don't have to.
1154 if any (def: def.value._type or "" == "order") defsFiltered.values then
1155 sortProperties defsFiltered.values
1156 else
1157 defsFiltered.values;
1158 in
1159 {
1160 values = defsSorted;
1161 inherit (defsFiltered) highestPrio;
1162 };
1163 defsFinal = defsFinal'.values;
1164
1165 # Type-check the remaining definitions, and merge them. Or throw if no definitions.
1166 mergedValue =
1167 if isDefined then
1168 if type.merge ? v2 then
1169 # check and merge share the same closure
1170 # .headError is either not-present, null, or a string describing the error
1171 if checkedAndMerged.headError or null != null then
1172 throw "A definition for option `${showOption loc}' is not of type `${type.description}'. TypeError: ${checkedAndMerged.headError.message}"
1173 else
1174 checkedAndMerged.value
1175 else if all (def: type.check def.value) defsFinal then
1176 type.merge loc defsFinal
1177 else
1178 let
1179 allInvalid = filter (def: !type.check def.value) defsFinal;
1180 in
1181 throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
1182 else
1183 # (nixos-option detects this specific error message and gives it special
1184 # handling. If changed here, please change it there too.)
1185 throw
1186 "The option `${showOption loc}' was accessed but has no value defined. Try setting the option.";
1187
1188 checkedAndMerged =
1189 (
1190 # This function (which is immediately applied) checks that type.merge
1191 # returns the proper attrset.
1192 # Once use of the merge.v2 feature has propagated, consider removing this
1193 # for an estimated one thousandth performance improvement (NixOS by nr.thunks).
1194 {
1195 headError,
1196 value,
1197 valueMeta,
1198 }@args:
1199 args
1200 )
1201 (
1202 if type.merge ? v2 then
1203 let
1204 r = type.merge.v2 {
1205 inherit loc;
1206 defs = defsFinal;
1207 };
1208 in
1209 r
1210 // {
1211 valueMeta = r.valueMeta // {
1212 _internal = {
1213 inherit type;
1214 };
1215 };
1216 }
1217 else
1218 {
1219 headError = null;
1220 value = mergedValue;
1221 valueMeta = { };
1222 }
1223 );
1224
1225 isDefined = defsFinal != [ ];
1226
1227 optionalValue = if isDefined then { value = mergedValue; } else { };
1228 };
1229
1230 /**
1231 Given a config set, expand mkMerge properties, and push down the
1232 other properties into the children. The result is a list of
1233 config sets that do not have properties at top-level. For
1234 example,
1235
1236 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
1237
1238 is transformed into
1239
1240 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
1241
1242 This transform is the critical step that allows mkIf conditions
1243 to refer to the full configuration without creating an infinite
1244 recursion.
1245
1246 # Inputs
1247
1248 `cfg`
1249
1250 : 1\. Function argument
1251 */
1252 pushDownProperties =
1253 cfg:
1254 if cfg._type or "" == "merge" then
1255 concatMap pushDownProperties cfg.contents
1256 else if cfg._type or "" == "if" then
1257 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
1258 else if cfg._type or "" == "override" then
1259 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
1260 # FIXME: handle mkOrder?
1261 else
1262 [ cfg ];
1263
1264 /**
1265 Given a config value, expand mkMerge properties, and discharge
1266 any mkIf conditions. That is, this is the place where mkIf
1267 conditions are actually evaluated. The result is a list of
1268 config values. For example, ‘mkIf false x’ yields ‘[]’,
1269 ‘mkIf true x’ yields ‘[x]’, and
1270
1271 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
1272
1273 yields ‘[ 1 2 ]’.
1274
1275 # Inputs
1276
1277 `def`
1278
1279 : 1\. Function argument
1280 */
1281 dischargeProperties =
1282 def:
1283 if def._type or "" == "merge" then
1284 concatMap dischargeProperties def.contents
1285 else if def._type or "" == "if" then
1286 if isBool def.condition then
1287 if def.condition then dischargeProperties def.content else [ ]
1288 else
1289 throw "‘mkIf’ called with a non-Boolean condition"
1290 else
1291 [ def ];
1292
1293 /**
1294 Given a list of config values, process the mkOverride properties,
1295 that is, return the values that have the highest (that is,
1296 numerically lowest) priority, and strip the mkOverride
1297 properties. For example,
1298
1299 [ { file = "/1"; value = mkOverride 10 "a"; }
1300 { file = "/2"; value = mkOverride 20 "b"; }
1301 { file = "/3"; value = "z"; }
1302 { file = "/4"; value = mkOverride 10 "d"; }
1303 ]
1304
1305 yields
1306
1307 [ { file = "/1"; value = "a"; }
1308 { file = "/4"; value = "d"; }
1309 ]
1310
1311 Note that "z" has the default priority 100.
1312
1313 # Inputs
1314
1315 `defs`
1316
1317 : 1\. Function argument
1318 */
1319 filterOverrides = defs: (filterOverrides' defs).values;
1320
1321 filterOverrides' =
1322 defs:
1323 let
1324 getPrio =
1325 def: if def.value._type or "" == "override" then def.value.priority else defaultOverridePriority;
1326 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
1327 strip =
1328 def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
1329 in
1330 {
1331 values = concatMap (def: if getPrio def == highestPrio then [ (strip def) ] else [ ]) defs;
1332 inherit highestPrio;
1333 };
1334
1335 /**
1336 Sort a list of properties. The sort priority of a property is
1337 defaultOrderPriority by default, but can be overridden by wrapping the property
1338 using mkOrder.
1339
1340 # Inputs
1341
1342 `defs`
1343
1344 : 1\. Function argument
1345 */
1346 sortProperties =
1347 defs:
1348 let
1349 strip =
1350 def:
1351 if def.value._type or "" == "order" then
1352 def
1353 // {
1354 value = def.value.content;
1355 inherit (def.value) priority;
1356 }
1357 else
1358 def;
1359 defs' = map strip defs;
1360 compare = a: b: (a.priority or defaultOrderPriority) < (b.priority or defaultOrderPriority);
1361 in
1362 sort compare defs';
1363
1364 # This calls substSubModules, whose entire purpose is only to ensure that
1365 # option declarations in submodules have accurate position information.
1366 # TODO: Merge this into mergeOptionDecls
1367 fixupOptionType =
1368 loc: opt:
1369 if opt.type.getSubModules or null == null then
1370 opt // { type = opt.type or types.unspecified; }
1371 else
1372 opt
1373 // {
1374 type = opt.type.substSubModules opt.options;
1375 options = [ ];
1376 };
1377
1378 /**
1379 Merge an option's definitions in a way that preserves the priority of the
1380 individual attributes in the option value.
1381
1382 This does not account for all option semantics, such as readOnly.
1383
1384 # Inputs
1385
1386 `opt`
1387
1388 : 1\. Function argument
1389
1390 # Type
1391
1392 ```
1393 option -> attrsOf { highestPrio, value }
1394 ```
1395 */
1396 mergeAttrDefinitionsWithPrio =
1397 opt:
1398 let
1399 defsByAttr = zipAttrs (
1400 concatLists (
1401 concatMap (
1402 { value, ... }@def:
1403 map (mapAttrsToList (
1404 k: value: {
1405 ${k} = def // {
1406 inherit value;
1407 };
1408 }
1409 )) (pushDownProperties value)
1410 ) opt.definitionsWithLocations
1411 )
1412 );
1413 in
1414 assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf";
1415 mapAttrs (
1416 k: v:
1417 let
1418 merging = mergeDefinitions (opt.loc ++ [ k ]) opt.type.nestedTypes.elemType v;
1419 in
1420 {
1421 value = merging.mergedValue;
1422 inherit (merging.defsFinal') highestPrio;
1423 }
1424 ) defsByAttr;
1425
1426 /**
1427 Properties.
1428
1429 # Inputs
1430
1431 `condition`
1432
1433 : 1\. Function argument
1434
1435 `content`
1436
1437 : 2\. Function argument
1438 */
1439
1440 mkIf = condition: content: {
1441 _type = "if";
1442 inherit condition content;
1443 };
1444
1445 mkAssert =
1446 assertion: message: content:
1447 mkIf (if assertion then true else throw "\nFailed assertion: ${message}") content;
1448
1449 mkMerge = contents: {
1450 _type = "merge";
1451 inherit contents;
1452 };
1453
1454 /**
1455 Return a definition with file location information.
1456 */
1457 mkDefinition = args@{ file, value, ... }: args // { _type = "definition"; };
1458
1459 mkOverride = priority: content: {
1460 _type = "override";
1461 inherit priority content;
1462 };
1463
1464 mkOptionDefault = mkOverride 1500; # priority of option defaults
1465 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
1466 defaultOverridePriority = 100;
1467 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
1468 mkForce = mkOverride 50;
1469 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
1470
1471 defaultPriority =
1472 warnIf (oldestSupportedReleaseIsAtLeast 2305)
1473 "lib.modules.defaultPriority is deprecated, please use lib.modules.defaultOverridePriority instead."
1474 defaultOverridePriority;
1475
1476 mkFixStrictness = warn "lib.mkFixStrictness has no effect and will be removed. It returns its argument unmodified, so you can just remove any calls." id;
1477
1478 mkOrder = priority: content: {
1479 _type = "order";
1480 inherit priority content;
1481 };
1482
1483 mkBefore = mkOrder 500;
1484 defaultOrderPriority = 1000;
1485 mkAfter = mkOrder 1500;
1486
1487 # Convenient property used to transfer all definitions and their
1488 # properties from one option to another. This property is useful for
1489 # renaming options, and also for including properties from another module
1490 # system, including sub-modules.
1491 #
1492 # { config, options, ... }:
1493 #
1494 # {
1495 # # 'bar' might not always be defined in the current module-set.
1496 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
1497 #
1498 # # 'barbaz' has to be defined in the current module-set.
1499 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
1500 # }
1501 #
1502 # Note, this is different than taking the value of the option and using it
1503 # as a definition, as the new definition will not keep the mkOverride /
1504 # mkDefault properties of the previous option.
1505 #
1506 mkAliasDefinitions = mkAliasAndWrapDefinitions id;
1507 mkAliasAndWrapDefinitions = wrap: option: mkAliasIfDef option (wrap (mkMerge option.definitions));
1508
1509 # Similar to mkAliasAndWrapDefinitions but copies over the priority from the
1510 # option as well.
1511 #
1512 # If a priority is not set, it assumes a priority of defaultOverridePriority.
1513 mkAliasAndWrapDefsWithPriority =
1514 wrap: option:
1515 let
1516 prio = option.highestPrio or defaultOverridePriority;
1517 defsWithPrio = map (mkOverride prio) option.definitions;
1518 in
1519 mkAliasIfDef option (wrap (mkMerge defsWithPrio));
1520
1521 mkAliasIfDef = option: mkIf (isOption option && option.isDefined);
1522
1523 /**
1524 Compatibility.
1525
1526 # Inputs
1527
1528 `modules`
1529
1530 : 1\. Function argument
1531
1532 `args`
1533
1534 : 2\. Function argument
1535 */
1536 fixMergeModules =
1537 modules: args:
1538 evalModules {
1539 inherit modules args;
1540 check = false;
1541 };
1542
1543 /**
1544 Return a module that causes a warning to be shown if the
1545 specified option is defined. For example,
1546
1547 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
1548
1549 causes a assertion if the user defines boot.loader.grub.bootDevice.
1550
1551 replacementInstructions is a string that provides instructions on
1552 how to achieve the same functionality without the removed option,
1553 or alternatively a reasoning why the functionality is not needed.
1554 replacementInstructions SHOULD be provided!
1555
1556 # Inputs
1557
1558 `optionName`
1559
1560 : 1\. Function argument
1561
1562 `replacementInstructions`
1563
1564 : 2\. Function argument
1565 */
1566 mkRemovedOptionModule =
1567 optionName: replacementInstructions:
1568 { options, ... }:
1569 {
1570 options = setAttrByPath optionName (mkOption {
1571 visible = false;
1572 apply =
1573 x:
1574 throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}";
1575 });
1576 config.assertions =
1577 let
1578 opt = getAttrFromPath optionName options;
1579 in
1580 [
1581 {
1582 assertion = !opt.isDefined;
1583 message = ''
1584 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
1585 ${replacementInstructions}
1586 '';
1587 }
1588 ];
1589 };
1590
1591 /**
1592 Return a module that causes a warning to be shown if the
1593 specified "from" option is defined; the defined value is however
1594 forwarded to the "to" option. This can be used to rename options
1595 while providing backward compatibility. For example,
1596
1597 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
1598
1599 forwards any definitions of boot.copyKernels to
1600 boot.loader.grub.copyKernels while printing a warning.
1601
1602 This also copies over the priority from the aliased option to the
1603 non-aliased option.
1604
1605 # Inputs
1606
1607 `from`
1608
1609 : 1\. Function argument
1610
1611 `to`
1612
1613 : 2\. Function argument
1614 */
1615 mkRenamedOptionModule =
1616 from: to:
1617 doRename {
1618 inherit from to;
1619 visible = false;
1620 warn = true;
1621 use = trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
1622 };
1623
1624 mkRenamedOptionModuleWith =
1625 {
1626 /**
1627 Old option path as list of strings.
1628 */
1629 from,
1630 /**
1631 New option path as list of strings.
1632 */
1633 to,
1634 /**
1635 Release number of the first release that contains the rename, ignoring backports.
1636 Set it to the upcoming release, matching the nixpkgs/.version file.
1637 */
1638 sinceRelease,
1639 }:
1640 doRename {
1641 inherit from to;
1642 visible = false;
1643 warn = oldestSupportedReleaseIsAtLeast sinceRelease;
1644 use = warnIf (oldestSupportedReleaseIsAtLeast sinceRelease) "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
1645 };
1646
1647 /**
1648 Return a module that causes a warning to be shown if any of the "from"
1649 option is defined; the defined values can be used in the "mergeFn" to set
1650 the "to" value.
1651 This function can be used to merge multiple options into one that has a
1652 different type.
1653
1654 "mergeFn" takes the module "config" as a parameter and must return a value
1655 of "to" option type.
1656
1657 mkMergedOptionModule
1658 [ [ "a" "b" "c" ]
1659 [ "d" "e" "f" ] ]
1660 [ "x" "y" "z" ]
1661 (config:
1662 let value = p: getAttrFromPath p config;
1663 in
1664 if (value [ "a" "b" "c" ]) == true then "foo"
1665 else if (value [ "d" "e" "f" ]) == true then "bar"
1666 else "baz")
1667
1668 - options.a.b.c is a removed boolean option
1669 - options.d.e.f is a removed boolean option
1670 - options.x.y.z is a new str option that combines a.b.c and d.e.f
1671 functionality
1672
1673 This show a warning if any a.b.c or d.e.f is set, and set the value of
1674 x.y.z to the result of the merge function
1675
1676 # Inputs
1677
1678 `from`
1679
1680 : 1\. Function argument
1681
1682 `to`
1683
1684 : 2\. Function argument
1685
1686 `mergeFn`
1687
1688 : 3\. Function argument
1689 */
1690 mkMergedOptionModule =
1691 from: to: mergeFn:
1692 { config, options, ... }:
1693 {
1694 options = foldl' recursiveUpdate { } (
1695 map (
1696 path:
1697 setAttrByPath path (mkOption {
1698 visible = false;
1699 # To use the value in mergeFn without triggering errors
1700 default = "_mkMergedOptionModule";
1701 })
1702 ) from
1703 );
1704
1705 config = {
1706 warnings = filter (x: x != "") (
1707 map (
1708 f:
1709 let
1710 val = getAttrFromPath f config;
1711 opt = getAttrFromPath f options;
1712 in
1713 optionalString (val != "_mkMergedOptionModule")
1714 "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."
1715 ) from
1716 );
1717 }
1718 // setAttrByPath to (
1719 mkMerge (
1720 optional (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) (mergeFn config)
1721 )
1722 );
1723 };
1724
1725 /**
1726 Single "from" version of mkMergedOptionModule.
1727 Return a module that causes a warning to be shown if the "from" option is
1728 defined; the defined value can be used in the "mergeFn" to set the "to"
1729 value.
1730 This function can be used to change an option into another that has a
1731 different type.
1732
1733 "mergeFn" takes the module "config" as a parameter and must return a value of
1734 "to" option type.
1735
1736 mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ]
1737 (config:
1738 let value = getAttrFromPath [ "a" "b" "c" ] config;
1739 in
1740 if value > 100 then "high"
1741 else "normal")
1742
1743 - options.a.b.c is a removed int option
1744 - options.x.y.z is a new str option that supersedes a.b.c
1745
1746 This show a warning if a.b.c is set, and set the value of x.y.z to the
1747 result of the change function
1748
1749 # Inputs
1750
1751 `from`
1752
1753 : 1\. Function argument
1754
1755 `to`
1756
1757 : 2\. Function argument
1758
1759 `changeFn`
1760
1761 : 3\. Function argument
1762 */
1763 mkChangedOptionModule =
1764 from: to: changeFn:
1765 mkMergedOptionModule [ from ] to changeFn;
1766
1767 /**
1768 Like ‘mkRenamedOptionModule’, but doesn't show a warning.
1769
1770 # Inputs
1771
1772 `from`
1773
1774 : 1\. Function argument
1775
1776 `to`
1777
1778 : 2\. Function argument
1779 */
1780 mkAliasOptionModule =
1781 from: to:
1782 doRename {
1783 inherit from to;
1784 visible = true;
1785 warn = false;
1786 use = id;
1787 };
1788
1789 /**
1790 Transitional version of mkAliasOptionModule that uses MD docs.
1791
1792 This function is no longer necessary and merely an alias of `mkAliasOptionModule`.
1793 */
1794 mkAliasOptionModuleMD = mkAliasOptionModule;
1795
1796 /**
1797 mkDerivedConfig : Option a -> (a -> Definition b) -> Definition b
1798
1799 Create config definitions with the same priority as the definition of another option.
1800 This should be used for option definitions where one option sets the value of another as a convenience.
1801 For instance a config file could be set with a `text` or `source` option, where text translates to a `source`
1802 value using `mkDerivedConfig options.text (pkgs.writeText "filename.conf")`.
1803
1804 It takes care of setting the right priority using `mkOverride`.
1805
1806 # Inputs
1807
1808 `opt`
1809
1810 : 1\. Function argument
1811
1812 `f`
1813
1814 : 2\. Function argument
1815 */
1816 # TODO: make the module system error message include information about `opt` in
1817 # error messages about conflicts. E.g. introduce a variation of `mkOverride` which
1818 # adds extra location context to the definition object. This will allow context to be added
1819 # to all messages that report option locations "this value was derived from <full option name>
1820 # which was defined in <locations>". It can provide a trace of options that contributed
1821 # to definitions.
1822 mkDerivedConfig = opt: f: mkOverride (opt.highestPrio or defaultOverridePriority) (f opt.value);
1823
1824 /**
1825 Return a module that help declares an option that has been renamed.
1826 When a value is defined for the old option, it is forwarded to the `to` option.
1827 */
1828 doRename =
1829 {
1830 # List of strings representing the attribute path of the old option.
1831 from,
1832 # List of strings representing the attribute path of the new option.
1833 to,
1834 # Boolean, whether the old option is to be included in documentation.
1835 visible,
1836 # Whether to warn when a value is defined for the old option.
1837 # NOTE: This requires the NixOS assertions module to be imported, so
1838 # - this generally does not work in submodules
1839 # - this may or may not work outside NixOS
1840 warn,
1841 # A function that is applied to the option value, to form the value
1842 # of the old `from` option.
1843 #
1844 # For example, the identity function can be passed, to return the option value unchanged.
1845 # ```nix
1846 # use = x: x;
1847 # ```
1848 #
1849 # To add a warning, you can pass the partially applied `warn` function.
1850 # ```nix
1851 # use = lib.warn "Obsolete option `${opt.old}' is used. Use `${opt.to}' instead.";
1852 # ```
1853 use,
1854 # Legacy option, enabled by default: whether to preserve the priority of definitions in `old`.
1855 withPriority ? true,
1856 # A boolean that defines the `mkIf` condition for `to`.
1857 # If the condition evaluates to `true`, and the `to` path points into an
1858 # `attrsOf (submodule ...)`, then `doRename` would cause an empty module to
1859 # be created, even if the `from` option is undefined.
1860 # By setting this to an expression that may return `false`, you can inhibit
1861 # this undesired behavior.
1862 #
1863 # Example:
1864 #
1865 # ```nix
1866 # { config, lib, ... }:
1867 # let
1868 # inherit (lib) mkOption mkEnableOption types doRename;
1869 # in
1870 # {
1871 # options = {
1872 #
1873 # # Old service
1874 # services.foo.enable = mkEnableOption "foo";
1875 #
1876 # # New multi-instance service
1877 # services.foos = mkOption {
1878 # type = types.attrsOf (types.submodule …);
1879 # };
1880 # };
1881 # imports = [
1882 # (doRename {
1883 # from = [ "services" "foo" "bar" ];
1884 # to = [ "services" "foos" "" "bar" ];
1885 # visible = true;
1886 # warn = false;
1887 # use = x: x;
1888 # withPriority = true;
1889 # # Only define services.foos."" if needed. (It's not just about `bar`)
1890 # condition = config.services.foo.enable;
1891 # })
1892 # ];
1893 # }
1894 # ```
1895 condition ? true,
1896 }:
1897 { config, options, ... }:
1898 let
1899 fromOpt = getAttrFromPath from options;
1900 toOf = attrByPath to (abort "Renaming error: option `${showOption to}' does not exist.");
1901 toType =
1902 let
1903 opt = attrByPath to { } options;
1904 in
1905 opt.type or (types.submodule { });
1906 in
1907 {
1908 options = setAttrByPath from (
1909 mkOption {
1910 inherit visible;
1911 description = "Alias of {option}`${showOption to}`.";
1912 apply = x: use (toOf config);
1913 }
1914 // optionalAttrs (toType != null) {
1915 type = toType;
1916 }
1917 );
1918 config = mkIf condition (mkMerge [
1919 (optionalAttrs (options ? warnings) {
1920 warnings =
1921 optional (warn && fromOpt.isDefined)
1922 "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'.";
1923 })
1924 (
1925 if withPriority then
1926 mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt
1927 else
1928 mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt
1929 )
1930 ]);
1931 };
1932
1933 /**
1934 `importApply file arg :: Path -> a -> Module`, where `import file :: a -> Module`
1935
1936 `importApply` imports a Nix expression file much like the module system would,
1937 after passing an extra positional argument to the function in the file.
1938
1939 This function should be used when declaring a module in a file that refers to
1940 values from a different scope, such as that in a flake.
1941
1942 It solves the problems of alternative solutions:
1943
1944 - While `importApply file arg` is _mostly_ equivalent to
1945 `import file arg`, the latter returns a module without a location,
1946 as `import` only returns the contained expression. This leads to worse
1947 error messages.
1948
1949 - Using `specialArgs` to provide arguments to all modules. This effectively
1950 creates an incomplete module, and requires the user of the module to
1951 manually pass the `specialArgs` to the configuration, which is error-prone,
1952 verbose, and unnecessary.
1953
1954 The nix file must contain a function that returns a module.
1955 A module may itself be a function, so the file is often a function with two
1956 positional arguments instead of one. See the example below.
1957
1958 This function does not add support for deduplication and `disabledModules`,
1959 although that could be achieved by wrapping the returned module and setting
1960 the `key` module attribute.
1961 The reason for this omission is that the file path is not guaranteed to be
1962 a unique identifier for the module, as two instances of the module may
1963 reference different `arg`s in their closures.
1964
1965 Example
1966
1967 # lib.nix
1968 imports = [
1969 (lib.modules.importApply ./module.nix { bar = bar; })
1970 ];
1971
1972 # module.nix
1973 { bar }:
1974 { lib, config, ... }:
1975 {
1976 options = ...;
1977 config = ... bar ...;
1978 }
1979 */
1980 importApply =
1981 modulePath: staticArg: lib.setDefaultModuleLocation modulePath (import modulePath staticArg);
1982
1983 /**
1984 Use this function to import a JSON file as NixOS configuration.
1985
1986 modules.importJSON :: path -> attrs
1987
1988 # Inputs
1989
1990 `file`
1991
1992 : 1\. Function argument
1993 */
1994 importJSON = file: {
1995 _file = file;
1996 config = lib.importJSON file;
1997 };
1998
1999 /**
2000 Use this function to import a TOML file as NixOS configuration.
2001
2002 modules.importTOML :: path -> attrs
2003
2004 # Inputs
2005
2006 `file`
2007
2008 : 1\. Function argument
2009 */
2010 importTOML = file: {
2011 _file = file;
2012 config = lib.importTOML file;
2013 };
2014
2015 private =
2016 mapAttrs
2017 (
2018 k:
2019 warn "External use of `lib.modules.${k}` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/."
2020 )
2021 {
2022 inherit
2023 applyModuleArgsIfFunction
2024 dischargeProperties
2025 mergeModules
2026 mergeModules'
2027 pushDownProperties
2028 unifyModuleSyntax
2029 ;
2030 collectModules = collectModules null;
2031 };
2032
2033 /**
2034 Error messages produced by the module system.
2035
2036 We factor these out to improve the flow when reading the code.
2037
2038 Functions in `messages` that produce error messages are spelled in
2039 lower_snake_case. This goes against the convention in order to make the
2040 error message implementation more readable, and to visually distinguish
2041 them from other functions in the module system.
2042 */
2043 messages =
2044 let
2045 inherit (lib.strings) concatMapStringsSep escapeNixString trim;
2046 /**
2047 "" or ", in file FOO"
2048 */
2049 into_fallback_file_maybe =
2050 file:
2051 optionalString (
2052 file != null && file != unknownModule
2053 ) ", while trying to load a module into ${toString file}";
2054
2055 into_prefix_maybe =
2056 prefix:
2057 optionalString (prefix != [ ]) ", while trying to load a module into ${code (showOption prefix)}";
2058
2059 /**
2060 Format text with one line break between each list item.
2061 */
2062 lines = concatMapStringsSep "\n" trim;
2063
2064 /**
2065 Format text with two line break between each list item.
2066 */
2067 paragraphs = concatMapStringsSep "\n\n" trim;
2068
2069 /**
2070 ```
2071 optionalMatch
2072 { foo = "Foo result";
2073 bar = "Bar result";
2074 } "foo"
2075 == [ "Foo result" ]
2076
2077 optionalMatch { foo = "Foo"; } "baz" == [ ]
2078
2079 optionalMatch { foo = "Foo"; } true == [ ]
2080 ```
2081 */
2082 optionalMatch =
2083 cases: value: if isString value && cases ? ${value} then [ cases.${value} ] else [ ];
2084
2085 # esc = builtins.fromJSON "\"\\u001b\"";
2086 esc = builtins.fromJSON "\"\\u001b\"";
2087 # Bold purple for warnings
2088 warn = s: "${esc}[1;35m${s}${esc}[0m";
2089 # Bold green for suggestions
2090 good = s: "${esc}[1;32m${s}${esc}[0m";
2091 # Bold, default color for code
2092 code = s: "${esc}[1m${s}${esc}[0m";
2093
2094 in
2095 {
2096
2097 /**
2098 When load a value with a (wrong) _type as a module
2099 */
2100 not_a_module =
2101 {
2102 fallbackFile,
2103 value,
2104 _type,
2105 expectedClass ? null,
2106 prefix,
2107 }:
2108 paragraphs (
2109 [
2110 ''
2111 Expected a module, but found a value of type ${warn (escapeNixString _type)}${into_fallback_file_maybe fallbackFile}${into_prefix_maybe prefix}.
2112 A module is typically loaded by adding it to the ${code "imports = [ ... ];"} attribute of an existing module, or in the ${code "modules = [ ... ];"} argument of various functions.
2113 Please make sure that each of the list items is a module, and not a different kind of value.
2114 ''
2115 ]
2116 ++ (optionalMatch {
2117 "configuration" = trim ''
2118 If you really mean to import this configuration, instead please only import the modules that make up the configuration.
2119 You may have to create a `let` binding, file or attribute to give yourself access to the relevant modules.
2120 While loading a configuration into the module system is a very sensible idea, it can not be done cleanly in practice.
2121 '';
2122 # ^^ Extended explanation: That's because a finalized configuration is more than just a set of modules. For instance, it has its own `specialArgs` that, by the nature of `specialArgs` can't be loaded through `imports` or the the `modules` argument. So instead, we have to ask you to extract the relevant modules and use those instead. This way, we keep the module system comparatively simple, and hopefully avoid a bad surprise down the line.
2123
2124 "flake" = lines (
2125 [
2126 (trim ''
2127 Perhaps you forgot to select an attribute name?
2128 Instead of, for example,
2129 ${warn "inputs.someflake"}
2130 you need to write something like
2131 ${warn "inputs.someflake"}${
2132 if expectedClass == null then
2133 good ".modules.someApp.default"
2134 else
2135 good ".modules.${expectedClass}.default"
2136
2137 }
2138 '')
2139 ]
2140 ++ optionalMatch {
2141 # We'll add no more than 5 custom suggestions here.
2142 # Please switch to `.modules.${class}` in your Module System application.
2143 "nixos" = trim ''
2144 or
2145 ${warn "inputs.someflake"}${good ".nixosModules.default"}
2146 '';
2147 "darwin" = trim ''
2148 or
2149 ${warn "inputs.someflake"}${good ".darwinModules.default"}
2150 '';
2151 } expectedClass
2152 );
2153 } _type)
2154 );
2155 };
2156
2157in
2158private
2159// {
2160 # NOTE: not all of these functions are necessarily public interfaces; some
2161 # are just needed by types.nix, but are not meant to be consumed
2162 # externally.
2163 inherit
2164 defaultOrderPriority
2165 defaultOverridePriority
2166 defaultPriority
2167 doRename
2168 evalModules
2169 evalOptionValue # for use by lib.types
2170 filterOverrides
2171 filterOverrides'
2172 fixMergeModules
2173 fixupOptionType # should be private?
2174 importApply
2175 importJSON
2176 importTOML
2177 mergeDefinitions
2178 mergeAttrDefinitionsWithPrio
2179 mergeOptionDecls # should be private?
2180 mkAfter
2181 mkAliasAndWrapDefinitions
2182 mkAliasAndWrapDefsWithPriority
2183 mkAliasDefinitions
2184 mkAliasIfDef
2185 mkAliasOptionModule
2186 mkAliasOptionModuleMD
2187 mkAssert
2188 mkBefore
2189 mkChangedOptionModule
2190 mkDefault
2191 mkDefinition
2192 mkDerivedConfig
2193 mkFixStrictness
2194 mkForce
2195 mkIf
2196 mkImageMediaOverride
2197 mkMerge
2198 mkMergedOptionModule
2199 mkOptionDefault
2200 mkOrder
2201 mkOverride
2202 mkRemovedOptionModule
2203 mkRenamedOptionModule
2204 mkRenamedOptionModuleWith
2205 mkVMOverride
2206 setDefaultModuleLocation
2207 sortProperties
2208 ;
2209}