Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1{ lib }: 2 3let 4 inherit (lib) 5 all 6 any 7 attrByPath 8 attrNames 9 catAttrs 10 concatLists 11 concatMap 12 concatStringsSep 13 elem 14 filter 15 findFirst 16 foldl' 17 getAttrFromPath 18 head 19 id 20 imap1 21 isAttrs 22 isBool 23 isFunction 24 isList 25 isString 26 length 27 mapAttrs 28 mapAttrsToList 29 mapAttrsRecursiveCond 30 min 31 optional 32 optionalAttrs 33 optionalString 34 recursiveUpdate 35 reverseList sort 36 setAttrByPath 37 toList 38 types 39 warnIf 40 zipAttrsWith 41 ; 42 inherit (lib.options) 43 isOption 44 mkOption 45 showDefs 46 showFiles 47 showOption 48 unknownModule 49 literalExpression 50 ; 51 52 showDeclPrefix = loc: decl: prefix: 53 " - option(s) with prefix `${showOption (loc ++ [prefix])}' in module `${decl._file}'"; 54 showRawDecls = loc: decls: 55 concatStringsSep "\n" 56 (sort (a: b: a < b) 57 (concatMap 58 (decl: map 59 (showDeclPrefix loc decl) 60 (attrNames decl.options) 61 ) 62 decls 63 )); 64 65in 66 67rec { 68 69 /* 70 Evaluate a set of modules. The result is a set with the attributes: 71 72 options: The nested set of all option declarations, 73 74 config: The nested set of all option values. 75 76 type: A module system type representing the module set as a submodule, 77 to be extended by configuration from the containing module set. 78 79 This is also available as the module argument moduleType. 80 81 extendModules: A function similar to evalModules but building on top 82 of the module set. Its arguments, modules and specialArgs are 83 added to the existing values. 84 85 Using extendModules a few times has no performance impact as long 86 as you only reference the final options and config. 87 If you do reference multiple config (or options) from before and 88 after extendModules, performance is the same as with multiple 89 evalModules invocations, because the new modules' ability to 90 override existing configuration fundamentally requires a new 91 fixpoint to be constructed. 92 93 This is also available as a module argument. 94 95 _module: A portion of the configuration tree which is elided from 96 config. It contains some values that are mostly internal to the 97 module system implementation. 98 99 !!! Please think twice before adding to this argument list! The more 100 that is specified here instead of in the modules themselves the harder 101 it is to transparently move a set of modules to be a submodule of another 102 config (as the proper arguments need to be replicated at each call to 103 evalModules) and the less declarative the module set is. */ 104 evalModules = evalModulesArgs@ 105 { modules 106 , prefix ? [] 107 , # This should only be used for special arguments that need to be evaluated 108 # when resolving module structure (like in imports). For everything else, 109 # there's _module.args. If specialArgs.modulesPath is defined it will be 110 # used as the base path for disabledModules. 111 specialArgs ? {} 112 , # This would be remove in the future, Prefer _module.args option instead. 113 args ? {} 114 , # This would be remove in the future, Prefer _module.check option instead. 115 check ? true 116 }: 117 let 118 withWarnings = x: 119 lib.warnIf (evalModulesArgs?args) "The args argument to evalModules is deprecated. Please set config._module.args instead." 120 lib.warnIf (evalModulesArgs?check) "The check argument to evalModules is deprecated. Please set config._module.check instead." 121 x; 122 123 legacyModules = 124 optional (evalModulesArgs?args) { 125 config = { 126 _module.args = args; 127 }; 128 } 129 ++ optional (evalModulesArgs?check) { 130 config = { 131 _module.check = mkDefault check; 132 }; 133 }; 134 regularModules = modules ++ legacyModules; 135 136 # This internal module declare internal options under the `_module' 137 # attribute. These options are fragile, as they are used by the 138 # module system to change the interpretation of modules. 139 # 140 # When extended with extendModules or moduleType, a fresh instance of 141 # this module is used, to avoid conflicts and allow chaining of 142 # extendModules. 143 internalModule = rec { 144 _file = "lib/modules.nix"; 145 146 key = _file; 147 148 options = { 149 _module.args = mkOption { 150 # Because things like `mkIf` are entirely useless for 151 # `_module.args` (because there's no way modules can check which 152 # arguments were passed), we'll use `lazyAttrsOf` which drops 153 # support for that, in turn it's lazy in its values. This means e.g. 154 # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't 155 # start a download when `pkgs` wasn't evaluated. 156 type = types.lazyAttrsOf types.raw; 157 # Only render documentation once at the root of the option tree, 158 # not for all individual submodules. 159 # Allow merging option decls to make this internal regardless. 160 ${if prefix == [] 161 then null # unset => visible 162 else "internal"} = true; 163 # TODO: Change the type of this option to a submodule with a 164 # freeformType, so that individual arguments can be documented 165 # separately 166 description = '' 167 Additional arguments passed to each module in addition to ones 168 like <literal>lib</literal>, <literal>config</literal>, 169 and <literal>pkgs</literal>, <literal>modulesPath</literal>. 170 </para> 171 <para> 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 <literal>name</literal> 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 </para> 180 <para> 181 Some arguments are already passed by default, of which the 182 following <emphasis>cannot</emphasis> be changed with this option: 183 <itemizedlist> 184 <listitem> 185 <para> 186 <varname>lib</varname>: The nixpkgs library. 187 </para> 188 </listitem> 189 <listitem> 190 <para> 191 <varname>config</varname>: The results of all options after merging the values from all modules together. 192 </para> 193 </listitem> 194 <listitem> 195 <para> 196 <varname>options</varname>: The options declared in all modules. 197 </para> 198 </listitem> 199 <listitem> 200 <para> 201 <varname>specialArgs</varname>: The <literal>specialArgs</literal> argument passed to <literal>evalModules</literal>. 202 </para> 203 </listitem> 204 <listitem> 205 <para> 206 All attributes of <varname>specialArgs</varname> 207 </para> 208 <para> 209 Whereas option values can generally depend on other option values 210 thanks to laziness, this does not apply to <literal>imports</literal>, which 211 must be computed statically before anything else. 212 </para> 213 <para> 214 For this reason, callers of the module system can provide <literal>specialArgs</literal> 215 which are available during import resolution. 216 </para> 217 <para> 218 For NixOS, <literal>specialArgs</literal> includes 219 <varname>modulesPath</varname>, which allows you to import 220 extra modules from the nixpkgs package tree without having to 221 somehow make the module aware of the location of the 222 <literal>nixpkgs</literal> or NixOS directories. 223 <programlisting> 224 { modulesPath, ... }: { 225 imports = [ 226 (modulesPath + "/profiles/minimal.nix") 227 ]; 228 } 229 </programlisting> 230 </para> 231 </listitem> 232 </itemizedlist> 233 </para> 234 <para> 235 For NixOS, the default value for this option includes at least this argument: 236 <itemizedlist> 237 <listitem> 238 <para> 239 <varname>pkgs</varname>: The nixpkgs package set according to 240 the <option>nixpkgs.pkgs</option> option. 241 </para> 242 </listitem> 243 </itemizedlist> 244 ''; 245 }; 246 247 _module.check = mkOption { 248 type = types.bool; 249 internal = true; 250 default = true; 251 description = "Whether to check whether all option definitions have matching declarations."; 252 }; 253 254 _module.freeformType = mkOption { 255 type = types.nullOr types.optionType; 256 internal = true; 257 default = null; 258 description = '' 259 If set, merge all definitions that don't have an associated option 260 together using this type. The result then gets combined with the 261 values of all declared options to produce the final <literal> 262 config</literal> value. 263 264 If this is <literal>null</literal>, definitions without an option 265 will throw an error unless <option>_module.check</option> is 266 turned off. 267 ''; 268 }; 269 270 _module.specialArgs = mkOption { 271 readOnly = true; 272 internal = true; 273 description = '' 274 Externally provided module arguments that can't be modified from 275 within a configuration, but can be used in module imports. 276 ''; 277 }; 278 }; 279 280 config = { 281 _module.args = { 282 inherit extendModules; 283 moduleType = type; 284 }; 285 _module.specialArgs = specialArgs; 286 }; 287 }; 288 289 merged = 290 let collected = collectModules 291 (specialArgs.modulesPath or "") 292 (regularModules ++ [ internalModule ]) 293 ({ inherit lib options config specialArgs; } // specialArgs); 294 in mergeModules prefix (reverseList collected); 295 296 options = merged.matchedOptions; 297 298 config = 299 let 300 301 # For definitions that have an associated option 302 declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options; 303 304 # If freeformType is set, this is for definitions that don't have an associated option 305 freeformConfig = 306 let 307 defs = map (def: { 308 file = def.file; 309 value = setAttrByPath def.prefix def.value; 310 }) merged.unmatchedDefns; 311 in if defs == [] then {} 312 else declaredConfig._module.freeformType.merge prefix defs; 313 314 in if declaredConfig._module.freeformType == null then declaredConfig 315 # Because all definitions that had an associated option ended in 316 # declaredConfig, freeformConfig can only contain the non-option 317 # paths, meaning recursiveUpdate will never override any value 318 else recursiveUpdate freeformConfig declaredConfig; 319 320 checkUnmatched = 321 if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then 322 let 323 firstDef = head merged.unmatchedDefns; 324 baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}"; 325 in 326 if attrNames options == [ "_module" ] 327 then 328 let 329 optionName = showOption prefix; 330 in 331 if optionName == "" 332 then throw '' 333 ${baseMsg} 334 335 It seems as if you're trying to declare an option by placing it into `config' rather than `options'! 336 '' 337 else 338 throw '' 339 ${baseMsg} 340 341 However there are no options defined in `${showOption prefix}'. Are you sure you've 342 declared your options properly? This can happen if you e.g. declared your options in `types.submodule' 343 under `config' rather than `options'. 344 '' 345 else throw baseMsg 346 else null; 347 348 checked = builtins.seq checkUnmatched; 349 350 extendModules = extendArgs@{ 351 modules ? [], 352 specialArgs ? {}, 353 prefix ? [], 354 }: 355 evalModules (evalModulesArgs // { 356 modules = regularModules ++ modules; 357 specialArgs = evalModulesArgs.specialArgs or {} // specialArgs; 358 prefix = extendArgs.prefix or evalModulesArgs.prefix or []; 359 }); 360 361 type = lib.types.submoduleWith { 362 inherit modules specialArgs; 363 }; 364 365 result = withWarnings { 366 options = checked options; 367 config = checked (removeAttrs config [ "_module" ]); 368 _module = checked (config._module); 369 inherit extendModules type; 370 }; 371 in result; 372 373 # collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ] 374 # 375 # Collects all modules recursively through `import` statements, filtering out 376 # all modules in disabledModules. 377 collectModules = let 378 379 # Like unifyModuleSyntax, but also imports paths and calls functions if necessary 380 loadModule = args: fallbackFile: fallbackKey: m: 381 if isFunction m || isAttrs m then 382 unifyModuleSyntax fallbackFile fallbackKey (applyModuleArgsIfFunction fallbackKey m args) 383 else if isList m then 384 let defs = [{ file = fallbackFile; value = m; }]; in 385 throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}" 386 else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args); 387 388 /* 389 Collects all modules recursively into the form 390 391 { 392 disabled = [ <list of disabled modules> ]; 393 # All modules of the main module list 394 modules = [ 395 { 396 key = <key1>; 397 module = <module for key1>; 398 # All modules imported by the module for key1 399 modules = [ 400 { 401 key = <key1-1>; 402 module = <module for key1-1>; 403 # All modules imported by the module for key1-1 404 modules = [ ... ]; 405 } 406 ... 407 ]; 408 } 409 ... 410 ]; 411 } 412 */ 413 collectStructuredModules = 414 let 415 collectResults = modules: { 416 disabled = concatLists (catAttrs "disabled" modules); 417 inherit modules; 418 }; 419 in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x: 420 let 421 module = loadModule args parentFile "${parentKey}:anon-${toString n}" x; 422 collectedImports = collectStructuredModules module._file module.key module.imports args; 423 in { 424 key = module.key; 425 module = module; 426 modules = collectedImports.modules; 427 disabled = module.disabledModules ++ collectedImports.disabled; 428 }) initialModules); 429 430 # filterModules :: String -> { disabled, modules } -> [ Module ] 431 # 432 # Filters a structure as emitted by collectStructuredModules by removing all disabled 433 # modules recursively. It returns the final list of unique-by-key modules 434 filterModules = modulesPath: { disabled, modules }: 435 let 436 moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m; 437 disabledKeys = map moduleKey disabled; 438 keyFilter = filter (attrs: ! elem attrs.key disabledKeys); 439 in map (attrs: attrs.module) (builtins.genericClosure { 440 startSet = keyFilter modules; 441 operator = attrs: keyFilter attrs.modules; 442 }); 443 444 in modulesPath: initialModules: args: 445 filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args); 446 447 /* Wrap a module with a default location for reporting errors. */ 448 setDefaultModuleLocation = file: m: 449 { _file = file; imports = [ m ]; }; 450 451 /* Massage a module into canonical form, that is, a set consisting 452 of options, config and imports attributes. */ 453 unifyModuleSyntax = file: key: m: 454 let 455 addMeta = config: if m ? meta 456 then mkMerge [ config { meta = m.meta; } ] 457 else config; 458 addFreeformType = config: if m ? freeformType 459 then mkMerge [ config { _module.freeformType = m.freeformType; } ] 460 else config; 461 in 462 if m ? config || m ? options then 463 let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in 464 if badAttrs != {} then 465 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." 466 else 467 { _file = toString m._file or file; 468 key = toString m.key or key; 469 disabledModules = m.disabledModules or []; 470 imports = m.imports or []; 471 options = m.options or {}; 472 config = addFreeformType (addMeta (m.config or {})); 473 } 474 else 475 lib.throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module." 476 { _file = toString m._file or file; 477 key = toString m.key or key; 478 disabledModules = m.disabledModules or []; 479 imports = m.require or [] ++ m.imports or []; 480 options = {}; 481 config = addFreeformType (addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"])); 482 }; 483 484 applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then 485 let 486 # Module arguments are resolved in a strict manner when attribute set 487 # deconstruction is used. As the arguments are now defined with the 488 # config._module.args option, the strictness used on the attribute 489 # set argument would cause an infinite loop, if the result of the 490 # option is given as argument. 491 # 492 # To work-around the strictness issue on the deconstruction of the 493 # attributes set argument, we create a new attribute set which is 494 # constructed to satisfy the expected set of attributes. Thus calling 495 # a module will resolve strictly the attributes used as argument but 496 # not their values. The values are forwarding the result of the 497 # evaluation of the option. 498 context = name: ''while evaluating the module argument `${name}' in "${key}":''; 499 extraArgs = builtins.mapAttrs (name: _: 500 builtins.addErrorContext (context name) 501 (args.${name} or config._module.args.${name}) 502 ) (lib.functionArgs f); 503 504 # Note: we append in the opposite order such that we can add an error 505 # context on the explicited arguments of "args" too. This update 506 # operator is used to make the "args@{ ... }: with args.lib;" notation 507 # works. 508 in f (args // extraArgs) 509 else 510 f; 511 512 /* Merge a list of modules. This will recurse over the option 513 declarations in all modules, combining them into a single set. 514 At the same time, for each option declaration, it will merge the 515 corresponding option definitions in all machines, returning them 516 in the value attribute of each option. 517 518 This returns a set like 519 { 520 # A recursive set of options along with their final values 521 matchedOptions = { 522 foo = { _type = "option"; value = "option value of foo"; ... }; 523 bar.baz = { _type = "option"; value = "option value of bar.baz"; ... }; 524 ... 525 }; 526 # A list of definitions that weren't matched by any option 527 unmatchedDefns = [ 528 { file = "file.nix"; prefix = [ "qux" ]; value = "qux"; } 529 ... 530 ]; 531 } 532 */ 533 mergeModules = prefix: modules: 534 mergeModules' prefix modules 535 (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules); 536 537 mergeModules' = prefix: options: configs: 538 let 539 /* byName is like foldAttrs, but will look for attributes to merge in the 540 specified attribute name. 541 542 byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"]) 543 [ 544 { 545 hidden="baz"; 546 foo={qux="bar"; gla="flop";}; 547 } 548 { 549 hidden="fli"; 550 foo={qux="gne"; gli="flip";}; 551 } 552 ] 553 ===> 554 { 555 gla = [ "module.hidden=baz,value=flop" ]; 556 gli = [ "module.hidden=fli,value=flip" ]; 557 qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ]; 558 } 559 */ 560 byName = attr: f: modules: 561 zipAttrsWith (n: concatLists) 562 (map (module: let subtree = module.${attr}; in 563 if !(builtins.isAttrs subtree) then 564 throw '' 565 You're trying to declare a value of type `${builtins.typeOf subtree}' 566 rather than an attribute-set for the option 567 `${builtins.concatStringsSep "." prefix}'! 568 569 This usually happens if `${builtins.concatStringsSep "." prefix}' has option 570 definitions inside that are not matched. Please check how to properly define 571 this option by e.g. referring to `man 5 configuration.nix'! 572 '' 573 else 574 mapAttrs (n: f module) subtree 575 ) modules); 576 # an attrset 'name' => list of submodules that declare ‘name’. 577 declsByName = byName "options" (module: option: 578 [{ inherit (module) _file; options = option; }] 579 ) options; 580 # an attrset 'name' => list of submodules that define ‘name’. 581 defnsByName = byName "config" (module: value: 582 map (config: { inherit (module) file; inherit config; }) (pushDownProperties value) 583 ) configs; 584 # extract the definitions for each loc 585 defnsByName' = byName "config" (module: value: 586 [{ inherit (module) file; inherit value; }] 587 ) configs; 588 589 # Convert an option tree decl to a submodule option decl 590 optionTreeToOption = decl: 591 if isOption decl.options 592 then decl 593 else decl // { 594 options = mkOption { 595 type = types.submoduleWith { 596 modules = [ { options = decl.options; } ]; 597 # `null` is not intended for use by modules. It is an internal 598 # value that means "whatever the user has declared elsewhere". 599 # This might become obsolete with https://github.com/NixOS/nixpkgs/issues/162398 600 shorthandOnlyDefinesConfig = null; 601 }; 602 }; 603 }; 604 605 resultsByName = mapAttrs (name: decls: 606 # We're descending into attribute ‘name’. 607 let 608 loc = prefix ++ [name]; 609 defns = defnsByName.${name} or []; 610 defns' = defnsByName'.${name} or []; 611 optionDecls = filter (m: isOption m.options) decls; 612 in 613 if length optionDecls == length decls then 614 let opt = fixupOptionType loc (mergeOptionDecls loc decls); 615 in { 616 matchedOptions = evalOptionValue loc opt defns'; 617 unmatchedDefns = []; 618 } 619 else if optionDecls != [] then 620 if all (x: x.options.type.name == "submodule") optionDecls 621 # Raw options can only be merged into submodules. Merging into 622 # attrsets might be nice, but ambiguous. Suppose we have 623 # attrset as a `attrsOf submodule`. User declares option 624 # attrset.foo.bar, this could mean: 625 # a. option `bar` is only available in `attrset.foo` 626 # b. option `foo.bar` is available in all `attrset.*` 627 # c. reject and require "<name>" as a reminder that it behaves like (b). 628 # d. magically combine (a) and (c). 629 # All of the above are merely syntax sugar though. 630 then 631 let opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls)); 632 in { 633 matchedOptions = evalOptionValue loc opt defns'; 634 unmatchedDefns = []; 635 } 636 else 637 let 638 firstNonOption = findFirst (m: !isOption m.options) "" decls; 639 nonOptions = filter (m: !isOption m.options) decls; 640 in 641 throw "The option `${showOption loc}' in module `${(lib.head optionDecls)._file}' would be a parent of the following options, but its type `${(lib.head optionDecls).options.type.description or "<no description>"}' does not support nested options.\n${ 642 showRawDecls loc nonOptions 643 }" 644 else 645 mergeModules' loc decls defns) declsByName; 646 647 matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName; 648 649 # an attrset 'name' => list of unmatched definitions for 'name' 650 unmatchedDefnsByName = 651 # Propagate all unmatched definitions from nested option sets 652 mapAttrs (n: v: v.unmatchedDefns) resultsByName 653 # Plus the definitions for the current prefix that don't have a matching option 654 // removeAttrs defnsByName' (attrNames matchedOptions); 655 in { 656 inherit matchedOptions; 657 658 # Transforms unmatchedDefnsByName into a list of definitions 659 unmatchedDefns = 660 if configs == [] 661 then 662 # When no config values exist, there can be no unmatched config, so 663 # we short circuit and avoid evaluating more _options_ than necessary. 664 [] 665 else 666 concatLists (mapAttrsToList (name: defs: 667 map (def: def // { 668 # Set this so we know when the definition first left unmatched territory 669 prefix = [name] ++ (def.prefix or []); 670 }) defs 671 ) unmatchedDefnsByName); 672 }; 673 674 /* Merge multiple option declarations into a single declaration. In 675 general, there should be only one declaration of each option. 676 The exception is the options attribute, which specifies 677 sub-options. These can be specified multiple times to allow one 678 module to add sub-options to an option declared somewhere else 679 (e.g. multiple modules define sub-options for fileSystems). 680 681 'loc' is the list of attribute names where the option is located. 682 683 'opts' is a list of modules. Each module has an options attribute which 684 correspond to the definition of 'loc' in 'opt.file'. */ 685 mergeOptionDecls = 686 let 687 coerceOption = file: opt: 688 if isFunction opt then setDefaultModuleLocation file opt 689 else setDefaultModuleLocation file { options = opt; }; 690 in loc: opts: 691 foldl' (res: opt: 692 let t = res.type; 693 t' = opt.options.type; 694 mergedType = t.typeMerge t'.functor; 695 typesMergeable = mergedType != null; 696 typeSet = if (bothHave "type") && typesMergeable 697 then { type = mergedType; } 698 else {}; 699 bothHave = k: opt.options ? ${k} && res ? ${k}; 700 in 701 if bothHave "default" || 702 bothHave "example" || 703 bothHave "description" || 704 bothHave "apply" || 705 (bothHave "type" && (! typesMergeable)) 706 then 707 throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}." 708 else 709 let 710 getSubModules = opt.options.type.getSubModules or null; 711 submodules = 712 if getSubModules != null then map (setDefaultModuleLocation opt._file) getSubModules ++ res.options 713 else res.options; 714 in opt.options // res // 715 { declarations = res.declarations ++ [opt._file]; 716 options = submodules; 717 } // typeSet 718 ) { inherit loc; declarations = []; options = []; } opts; 719 720 /* Merge all the definitions of an option to produce the final 721 config value. */ 722 evalOptionValue = loc: opt: defs: 723 let 724 # Add in the default value for this option, if any. 725 defs' = 726 (optional (opt ? default) 727 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; 728 729 # Handle properties, check types, and merge everything together. 730 res = 731 if opt.readOnly or false && length defs' > 1 then 732 let 733 # For a better error message, evaluate all readOnly definitions as 734 # if they were the only definition. 735 separateDefs = map (def: def // { 736 value = (mergeDefinitions loc opt.type [ def ]).mergedValue; 737 }) defs'; 738 in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}" 739 else 740 mergeDefinitions loc opt.type defs'; 741 742 # Apply the 'apply' function to the merged value. This allows options to 743 # yield a value computed from the definitions 744 value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue; 745 746 warnDeprecation = 747 warnIf (opt.type.deprecationMessage != null) 748 "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}"; 749 750 in warnDeprecation opt // 751 { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value; 752 inherit (res.defsFinal') highestPrio; 753 definitions = map (def: def.value) res.defsFinal; 754 files = map (def: def.file) res.defsFinal; 755 inherit (res) isDefined; 756 # This allows options to be correctly displayed using `${options.path.to.it}` 757 __toString = _: showOption loc; 758 }; 759 760 # Merge definitions of a value of a given type. 761 mergeDefinitions = loc: type: defs: rec { 762 defsFinal' = 763 let 764 # Process mkMerge and mkIf properties. 765 defs' = concatMap (m: 766 map (value: { inherit (m) file; inherit value; }) (builtins.addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value)) 767 ) defs; 768 769 # Process mkOverride properties. 770 defs'' = filterOverrides' defs'; 771 772 # Sort mkOrder properties. 773 defs''' = 774 # Avoid sorting if we don't have to. 775 if any (def: def.value._type or "" == "order") defs''.values 776 then sortProperties defs''.values 777 else defs''.values; 778 in { 779 values = defs'''; 780 inherit (defs'') highestPrio; 781 }; 782 defsFinal = defsFinal'.values; 783 784 # Type-check the remaining definitions, and merge them. Or throw if no definitions. 785 mergedValue = 786 if isDefined then 787 if all (def: type.check def.value) defsFinal then type.merge loc defsFinal 788 else let allInvalid = filter (def: ! type.check def.value) defsFinal; 789 in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}" 790 else 791 # (nixos-option detects this specific error message and gives it special 792 # handling. If changed here, please change it there too.) 793 throw "The option `${showOption loc}' is used but not defined."; 794 795 isDefined = defsFinal != []; 796 797 optionalValue = 798 if isDefined then { value = mergedValue; } 799 else {}; 800 }; 801 802 /* Given a config set, expand mkMerge properties, and push down the 803 other properties into the children. The result is a list of 804 config sets that do not have properties at top-level. For 805 example, 806 807 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ] 808 809 is transformed into 810 811 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ]. 812 813 This transform is the critical step that allows mkIf conditions 814 to refer to the full configuration without creating an infinite 815 recursion. 816 */ 817 pushDownProperties = cfg: 818 if cfg._type or "" == "merge" then 819 concatMap pushDownProperties cfg.contents 820 else if cfg._type or "" == "if" then 821 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) 822 else if cfg._type or "" == "override" then 823 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) 824 else # FIXME: handle mkOrder? 825 [ cfg ]; 826 827 /* Given a config value, expand mkMerge properties, and discharge 828 any mkIf conditions. That is, this is the place where mkIf 829 conditions are actually evaluated. The result is a list of 830 config values. For example, mkIf false x yields [], 831 mkIf true x yields [x], and 832 833 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ] 834 835 yields [ 1 2 ]. 836 */ 837 dischargeProperties = def: 838 if def._type or "" == "merge" then 839 concatMap dischargeProperties def.contents 840 else if def._type or "" == "if" then 841 if isBool def.condition then 842 if def.condition then 843 dischargeProperties def.content 844 else 845 [ ] 846 else 847 throw "mkIf called with a non-Boolean condition" 848 else 849 [ def ]; 850 851 /* Given a list of config values, process the mkOverride properties, 852 that is, return the values that have the highest (that is, 853 numerically lowest) priority, and strip the mkOverride 854 properties. For example, 855 856 [ { file = "/1"; value = mkOverride 10 "a"; } 857 { file = "/2"; value = mkOverride 20 "b"; } 858 { file = "/3"; value = "z"; } 859 { file = "/4"; value = mkOverride 10 "d"; } 860 ] 861 862 yields 863 864 [ { file = "/1"; value = "a"; } 865 { file = "/4"; value = "d"; } 866 ] 867 868 Note that "z" has the default priority 100. 869 */ 870 filterOverrides = defs: (filterOverrides' defs).values; 871 872 filterOverrides' = defs: 873 let 874 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPriority; 875 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs; 876 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; 877 in { 878 values = concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs; 879 inherit highestPrio; 880 }; 881 882 /* Sort a list of properties. The sort priority of a property is 883 1000 by default, but can be overridden by wrapping the property 884 using mkOrder. */ 885 sortProperties = defs: 886 let 887 strip = def: 888 if def.value._type or "" == "order" 889 then def // { value = def.value.content; inherit (def.value) priority; } 890 else def; 891 defs' = map strip defs; 892 compare = a: b: (a.priority or 1000) < (b.priority or 1000); 893 in sort compare defs'; 894 895 # This calls substSubModules, whose entire purpose is only to ensure that 896 # option declarations in submodules have accurate position information. 897 # TODO: Merge this into mergeOptionDecls 898 fixupOptionType = loc: opt: 899 if opt.type.getSubModules or null == null 900 then opt // { type = opt.type or types.unspecified; } 901 else opt // { type = opt.type.substSubModules opt.options; options = []; }; 902 903 904 /* Properties. */ 905 906 mkIf = condition: content: 907 { _type = "if"; 908 inherit condition content; 909 }; 910 911 mkAssert = assertion: message: content: 912 mkIf 913 (if assertion then true else throw "\nFailed assertion: ${message}") 914 content; 915 916 mkMerge = contents: 917 { _type = "merge"; 918 inherit contents; 919 }; 920 921 mkOverride = priority: content: 922 { _type = "override"; 923 inherit priority content; 924 }; 925 926 mkOptionDefault = mkOverride 1500; # priority of option defaults 927 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default 928 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 929 mkForce = mkOverride 50; 930 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’ 931 932 mkFixStrictness = lib.warn "lib.mkFixStrictness has no effect and will be removed. It returns its argument unmodified, so you can just remove any calls." id; 933 934 mkOrder = priority: content: 935 { _type = "order"; 936 inherit priority content; 937 }; 938 939 mkBefore = mkOrder 500; 940 mkAfter = mkOrder 1500; 941 942 # The default priority for things that don't have a priority specified. 943 defaultPriority = 100; 944 945 # Convenient property used to transfer all definitions and their 946 # properties from one option to another. This property is useful for 947 # renaming options, and also for including properties from another module 948 # system, including sub-modules. 949 # 950 # { config, options, ... }: 951 # 952 # { 953 # # 'bar' might not always be defined in the current module-set. 954 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {}); 955 # 956 # # 'barbaz' has to be defined in the current module-set. 957 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths; 958 # } 959 # 960 # Note, this is different than taking the value of the option and using it 961 # as a definition, as the new definition will not keep the mkOverride / 962 # mkDefault properties of the previous option. 963 # 964 mkAliasDefinitions = mkAliasAndWrapDefinitions id; 965 mkAliasAndWrapDefinitions = wrap: option: 966 mkAliasIfDef option (wrap (mkMerge option.definitions)); 967 968 # Similar to mkAliasAndWrapDefinitions but copies over the priority from the 969 # option as well. 970 # 971 # If a priority is not set, it assumes a priority of defaultPriority. 972 mkAliasAndWrapDefsWithPriority = wrap: option: 973 let 974 prio = option.highestPrio or defaultPriority; 975 defsWithPrio = map (mkOverride prio) option.definitions; 976 in mkAliasIfDef option (wrap (mkMerge defsWithPrio)); 977 978 mkAliasIfDef = option: 979 mkIf (isOption option && option.isDefined); 980 981 /* Compatibility. */ 982 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; 983 984 985 /* Return a module that causes a warning to be shown if the 986 specified option is defined. For example, 987 988 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>" 989 990 causes a assertion if the user defines boot.loader.grub.bootDevice. 991 992 replacementInstructions is a string that provides instructions on 993 how to achieve the same functionality without the removed option, 994 or alternatively a reasoning why the functionality is not needed. 995 replacementInstructions SHOULD be provided! 996 */ 997 mkRemovedOptionModule = optionName: replacementInstructions: 998 { options, ... }: 999 { options = setAttrByPath optionName (mkOption { 1000 visible = false; 1001 apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}"; 1002 }); 1003 config.assertions = 1004 let opt = getAttrFromPath optionName options; in [{ 1005 assertion = !opt.isDefined; 1006 message = '' 1007 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. 1008 ${replacementInstructions} 1009 ''; 1010 }]; 1011 }; 1012 1013 /* Return a module that causes a warning to be shown if the 1014 specified "from" option is defined; the defined value is however 1015 forwarded to the "to" option. This can be used to rename options 1016 while providing backward compatibility. For example, 1017 1018 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ] 1019 1020 forwards any definitions of boot.copyKernels to 1021 boot.loader.grub.copyKernels while printing a warning. 1022 1023 This also copies over the priority from the aliased option to the 1024 non-aliased option. 1025 */ 1026 mkRenamedOptionModule = from: to: doRename { 1027 inherit from to; 1028 visible = false; 1029 warn = true; 1030 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; 1031 }; 1032 1033 mkRenamedOptionModuleWith = { 1034 /* Old option path as list of strings. */ 1035 from, 1036 /* New option path as list of strings. */ 1037 to, 1038 1039 /* 1040 Release number of the first release that contains the rename, ignoring backports. 1041 Set it to the upcoming release, matching the nixpkgs/.version file. 1042 */ 1043 sinceRelease, 1044 1045 }: doRename { 1046 inherit from to; 1047 visible = false; 1048 warn = lib.isInOldestRelease sinceRelease; 1049 use = lib.warnIf (lib.isInOldestRelease sinceRelease) 1050 "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; 1051 }; 1052 1053 /* Return a module that causes a warning to be shown if any of the "from" 1054 option is defined; the defined values can be used in the "mergeFn" to set 1055 the "to" value. 1056 This function can be used to merge multiple options into one that has a 1057 different type. 1058 1059 "mergeFn" takes the module "config" as a parameter and must return a value 1060 of "to" option type. 1061 1062 mkMergedOptionModule 1063 [ [ "a" "b" "c" ] 1064 [ "d" "e" "f" ] ] 1065 [ "x" "y" "z" ] 1066 (config: 1067 let value = p: getAttrFromPath p config; 1068 in 1069 if (value [ "a" "b" "c" ]) == true then "foo" 1070 else if (value [ "d" "e" "f" ]) == true then "bar" 1071 else "baz") 1072 1073 - options.a.b.c is a removed boolean option 1074 - options.d.e.f is a removed boolean option 1075 - options.x.y.z is a new str option that combines a.b.c and d.e.f 1076 functionality 1077 1078 This show a warning if any a.b.c or d.e.f is set, and set the value of 1079 x.y.z to the result of the merge function 1080 */ 1081 mkMergedOptionModule = from: to: mergeFn: 1082 { config, options, ... }: 1083 { 1084 options = foldl' recursiveUpdate {} (map (path: setAttrByPath path (mkOption { 1085 visible = false; 1086 # To use the value in mergeFn without triggering errors 1087 default = "_mkMergedOptionModule"; 1088 })) from); 1089 1090 config = { 1091 warnings = filter (x: x != "") (map (f: 1092 let val = getAttrFromPath f config; 1093 opt = getAttrFromPath f options; 1094 in 1095 optionalString 1096 (val != "_mkMergedOptionModule") 1097 "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." 1098 ) from); 1099 } // setAttrByPath to (mkMerge 1100 (optional 1101 (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) 1102 (mergeFn config))); 1103 }; 1104 1105 /* Single "from" version of mkMergedOptionModule. 1106 Return a module that causes a warning to be shown if the "from" option is 1107 defined; the defined value can be used in the "mergeFn" to set the "to" 1108 value. 1109 This function can be used to change an option into another that has a 1110 different type. 1111 1112 "mergeFn" takes the module "config" as a parameter and must return a value of 1113 "to" option type. 1114 1115 mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ] 1116 (config: 1117 let value = getAttrFromPath [ "a" "b" "c" ] config; 1118 in 1119 if value > 100 then "high" 1120 else "normal") 1121 1122 - options.a.b.c is a removed int option 1123 - options.x.y.z is a new str option that supersedes a.b.c 1124 1125 This show a warning if a.b.c is set, and set the value of x.y.z to the 1126 result of the change function 1127 */ 1128 mkChangedOptionModule = from: to: changeFn: 1129 mkMergedOptionModule [ from ] to changeFn; 1130 1131 /* Like mkRenamedOptionModule, but doesn't show a warning. */ 1132 mkAliasOptionModule = from: to: doRename { 1133 inherit from to; 1134 visible = true; 1135 warn = false; 1136 use = id; 1137 }; 1138 1139 /* mkDerivedConfig : Option a -> (a -> Definition b) -> Definition b 1140 1141 Create config definitions with the same priority as the definition of another option. 1142 This should be used for option definitions where one option sets the value of another as a convenience. 1143 For instance a config file could be set with a `text` or `source` option, where text translates to a `source` 1144 value using `mkDerivedConfig options.text (pkgs.writeText "filename.conf")`. 1145 1146 It takes care of setting the right priority using `mkOverride`. 1147 */ 1148 # TODO: make the module system error message include information about `opt` in 1149 # error messages about conflicts. E.g. introduce a variation of `mkOverride` which 1150 # adds extra location context to the definition object. This will allow context to be added 1151 # to all messages that report option locations "this value was derived from <full option name> 1152 # which was defined in <locations>". It can provide a trace of options that contributed 1153 # to definitions. 1154 mkDerivedConfig = opt: f: 1155 mkOverride 1156 (opt.highestPrio or defaultPriority) 1157 (f opt.value); 1158 1159 doRename = { from, to, visible, warn, use, withPriority ? true }: 1160 { config, options, ... }: 1161 let 1162 fromOpt = getAttrFromPath from options; 1163 toOf = attrByPath to 1164 (abort "Renaming error: option `${showOption to}' does not exist."); 1165 toType = let opt = attrByPath to {} options; in opt.type or (types.submodule {}); 1166 in 1167 { 1168 options = setAttrByPath from (mkOption { 1169 inherit visible; 1170 description = "Alias of <option>${showOption to}</option>."; 1171 apply = x: use (toOf config); 1172 } // optionalAttrs (toType != null) { 1173 type = toType; 1174 }); 1175 config = mkMerge [ 1176 { 1177 warnings = optional (warn && fromOpt.isDefined) 1178 "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; 1179 } 1180 (if withPriority 1181 then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt 1182 else mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt) 1183 ]; 1184 }; 1185 1186 /* Use this function to import a JSON file as NixOS configuration. 1187 1188 modules.importJSON :: path -> attrs 1189 */ 1190 importJSON = file: { 1191 _file = file; 1192 config = lib.importJSON file; 1193 }; 1194 1195 /* Use this function to import a TOML file as NixOS configuration. 1196 1197 modules.importTOML :: path -> attrs 1198 */ 1199 importTOML = file: { 1200 _file = file; 1201 config = lib.importTOML file; 1202 }; 1203}