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