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