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