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