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