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