Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at release-19.03 734 lines 29 kB view raw
1{ lib }: 2 3with lib.lists; 4with lib.strings; 5with lib.trivial; 6with lib.attrsets; 7with lib.options; 8with lib.debug; 9with lib.types; 10 11rec { 12 13 /* Evaluate a set of modules. The result is a set of two 14 attributes: options: the nested set of all option declarations, 15 and config: the nested set of all option values. 16 !!! Please think twice before adding to this argument list! The more 17 that is specified here instead of in the modules themselves the harder 18 it is to transparently move a set of modules to be a submodule of another 19 config (as the proper arguments need to be replicated at each call to 20 evalModules) and the less declarative the module set is. */ 21 evalModules = { modules 22 , prefix ? [] 23 , # This should only be used for special arguments that need to be evaluated 24 # when resolving module structure (like in imports). For everything else, 25 # there's _module.args. If specialArgs.modulesPath is defined it will be 26 # used as the base path for disabledModules. 27 specialArgs ? {} 28 , # This would be remove in the future, Prefer _module.args option instead. 29 args ? {} 30 , # This would be remove in the future, Prefer _module.check option instead. 31 check ? true 32 }: 33 let 34 # This internal module declare internal options under the `_module' 35 # attribute. These options are fragile, as they are used by the 36 # module system to change the interpretation of modules. 37 internalModule = rec { 38 _file = ./modules.nix; 39 40 key = _file; 41 42 options = { 43 _module.args = mkOption { 44 type = types.attrsOf types.unspecified; 45 internal = true; 46 description = "Arguments passed to each module."; 47 }; 48 49 _module.check = mkOption { 50 type = types.bool; 51 internal = true; 52 default = check; 53 description = "Whether to check whether all option definitions have matching declarations."; 54 }; 55 }; 56 57 config = { 58 _module.args = args; 59 }; 60 }; 61 62 closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options lib; } // specialArgs); 63 64 options = mergeModules prefix (reverseList (filterModules (specialArgs.modulesPath or "") closed)); 65 66 # Traverse options and extract the option values into the final 67 # config set. At the same time, check whether all option 68 # definitions have matching declarations. 69 # !!! _module.check's value can't depend on any other config values 70 # without an infinite recursion. One way around this is to make the 71 # 'config' passed around to the modules be unconditionally unchecked, 72 # and only do the check in 'result'. 73 config = yieldConfig prefix options; 74 yieldConfig = prefix: set: 75 let res = removeAttrs (mapAttrs (n: v: 76 if isOption v then v.value 77 else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"]; 78 in 79 if options._module.check.value && set ? _definedNames then 80 foldl' (res: m: 81 foldl' (res: name: 82 if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.") 83 res m.names) 84 res set._definedNames 85 else 86 res; 87 result = { inherit options config; }; 88 in result; 89 90 91 # Filter disabled modules. Modules can be disabled allowing 92 # their implementation to be replaced. 93 filterModules = modulesPath: modules: 94 let 95 moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m; 96 disabledKeys = map moduleKey (concatMap (m: m.disabledModules) modules); 97 in 98 filter (m: !(elem m.key disabledKeys)) modules; 99 100 /* Close a set of modules under the imports relation. */ 101 closeModules = modules: args: 102 let 103 toClosureList = file: parentKey: imap1 (n: x: 104 if isAttrs x || isFunction x then 105 let key = "${parentKey}:anon-${toString n}"; in 106 unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args) 107 else 108 let file = toString x; key = toString x; in 109 unifyModuleSyntax file key (applyIfFunction key (import x) args)); 110 in 111 builtins.genericClosure { 112 startSet = toClosureList unknownModule "" modules; 113 operator = m: toClosureList m.file m.key m.imports; 114 }; 115 116 /* Massage a module into canonical form, that is, a set consisting 117 of options, config and imports attributes. */ 118 unifyModuleSyntax = file: key: m: 119 let metaSet = if m ? meta 120 then { meta = m.meta; } 121 else {}; 122 in 123 if m ? config || m ? options then 124 let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in 125 if badAttrs != {} then 126 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'." 127 else 128 { file = m._file or file; 129 key = toString m.key or key; 130 disabledModules = m.disabledModules or []; 131 imports = m.imports or []; 132 options = m.options or {}; 133 config = mkMerge [ (m.config or {}) metaSet ]; 134 } 135 else 136 { file = m._file or file; 137 key = toString m.key or key; 138 disabledModules = m.disabledModules or []; 139 imports = m.require or [] ++ m.imports or []; 140 options = {}; 141 config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ]; 142 }; 143 144 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then 145 let 146 # Module arguments are resolved in a strict manner when attribute set 147 # deconstruction is used. As the arguments are now defined with the 148 # config._module.args option, the strictness used on the attribute 149 # set argument would cause an infinite loop, if the result of the 150 # option is given as argument. 151 # 152 # To work-around the strictness issue on the deconstruction of the 153 # attributes set argument, we create a new attribute set which is 154 # constructed to satisfy the expected set of attributes. Thus calling 155 # a module will resolve strictly the attributes used as argument but 156 # not their values. The values are forwarding the result of the 157 # evaluation of the option. 158 requiredArgs = builtins.attrNames (lib.functionArgs f); 159 context = name: ''while evaluating the module argument `${name}' in "${key}":''; 160 extraArgs = builtins.listToAttrs (map (name: { 161 inherit name; 162 value = builtins.addErrorContext (context name) 163 (args.${name} or config._module.args.${name}); 164 }) requiredArgs); 165 166 # Note: we append in the opposite order such that we can add an error 167 # context on the explicited arguments of "args" too. This update 168 # operator is used to make the "args@{ ... }: with args.lib;" notation 169 # works. 170 in f (args // extraArgs) 171 else 172 f; 173 174 /* We have to pack and unpack submodules. We cannot wrap the expected 175 result of the function as we would no longer be able to list the arguments 176 of the submodule. (see applyIfFunction) */ 177 unpackSubmodule = unpack: m: args: 178 if isType "submodule" m then 179 { _file = m.file; } // (unpack m.submodule args) 180 else unpack m args; 181 182 packSubmodule = file: m: 183 { _type = "submodule"; file = file; submodule = m; }; 184 185 /* Merge a list of modules. This will recurse over the option 186 declarations in all modules, combining them into a single set. 187 At the same time, for each option declaration, it will merge the 188 corresponding option definitions in all machines, returning them 189 in the value attribute of each option. */ 190 mergeModules = prefix: modules: 191 mergeModules' prefix modules 192 (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules); 193 194 mergeModules' = prefix: options: configs: 195 let 196 /* byName is like foldAttrs, but will look for attributes to merge in the 197 specified attribute name. 198 199 byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"]) 200 [ 201 { 202 hidden="baz"; 203 foo={qux="bar"; gla="flop";}; 204 } 205 { 206 hidden="fli"; 207 foo={qux="gne"; gli="flip";}; 208 } 209 ] 210 ===> 211 { 212 gla = [ "module.hidden=baz,value=flop" ]; 213 gli = [ "module.hidden=fli,value=flip" ]; 214 qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ]; 215 } 216 */ 217 byName = attr: f: modules: 218 foldl' (acc: module: 219 acc // (mapAttrs (n: v: 220 (acc.${n} or []) ++ f module v 221 ) module.${attr} 222 ) 223 ) {} modules; 224 # an attrset 'name' => list of submodules that declare ‘name’. 225 declsByName = byName "options" (module: option: 226 [{ inherit (module) file; options = option; }] 227 ) options; 228 # an attrset 'name' => list of submodules that define ‘name’. 229 defnsByName = byName "config" (module: value: 230 map (config: { inherit (module) file; inherit config; }) (pushDownProperties value) 231 ) configs; 232 # extract the definitions for each loc 233 defnsByName' = byName "config" (module: value: 234 [{ inherit (module) file; inherit value; }] 235 ) configs; 236 in 237 (flip mapAttrs declsByName (name: decls: 238 # We're descending into attribute ‘name’. 239 let 240 loc = prefix ++ [name]; 241 defns = defnsByName.${name} or []; 242 defns' = defnsByName'.${name} or []; 243 nrOptions = count (m: isOption m.options) decls; 244 in 245 if nrOptions == length decls then 246 let opt = fixupOptionType loc (mergeOptionDecls loc decls); 247 in evalOptionValue loc opt defns' 248 else if nrOptions != 0 then 249 let 250 firstOption = findFirst (m: isOption m.options) "" decls; 251 firstNonOption = findFirst (m: !isOption m.options) "" decls; 252 in 253 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." 254 else 255 mergeModules' loc decls defns 256 )) 257 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; 258 259 /* Merge multiple option declarations into a single declaration. In 260 general, there should be only one declaration of each option. 261 The exception is the options attribute, which specifies 262 sub-options. These can be specified multiple times to allow one 263 module to add sub-options to an option declared somewhere else 264 (e.g. multiple modules define sub-options for fileSystems). 265 266 'loc' is the list of attribute names where the option is located. 267 268 'opts' is a list of modules. Each module has an options attribute which 269 correspond to the definition of 'loc' in 'opt.file'. */ 270 mergeOptionDecls = loc: opts: 271 foldl' (res: opt: 272 let t = res.type; 273 t' = opt.options.type; 274 mergedType = t.typeMerge t'.functor; 275 typesMergeable = mergedType != null; 276 typeSet = if (bothHave "type") && typesMergeable 277 then { type = mergedType; } 278 else {}; 279 bothHave = k: opt.options ? ${k} && res ? ${k}; 280 in 281 if bothHave "default" || 282 bothHave "example" || 283 bothHave "description" || 284 bothHave "apply" || 285 (bothHave "type" && (! typesMergeable)) 286 then 287 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}." 288 else 289 let 290 /* Add the modules of the current option to the list of modules 291 already collected. The options attribute except either a list of 292 submodules or a submodule. For each submodule, we add the file of the 293 current option declaration as the file use for the submodule. If the 294 submodule defines any filename, then we ignore the enclosing option file. */ 295 options' = toList opt.options.options; 296 coerceOption = file: opt: 297 if isFunction opt then packSubmodule file opt 298 else packSubmodule file { options = opt; }; 299 getSubModules = opt.options.type.getSubModules or null; 300 submodules = 301 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options 302 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options 303 else res.options; 304 in opt.options // res // 305 { declarations = res.declarations ++ [opt.file]; 306 options = submodules; 307 } // typeSet 308 ) { inherit loc; declarations = []; options = []; } opts; 309 310 /* Merge all the definitions of an option to produce the final 311 config value. */ 312 evalOptionValue = loc: opt: defs: 313 let 314 # Add in the default value for this option, if any. 315 defs' = 316 (optional (opt ? default) 317 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; 318 319 # Handle properties, check types, and merge everything together. 320 res = 321 if opt.readOnly or false && length defs' > 1 then 322 throw "The option `${showOption loc}' is read-only, but it's set multiple times." 323 else 324 mergeDefinitions loc opt.type defs'; 325 326 # Check whether the option is defined, and apply the ‘apply’ 327 # function to the merged value. This allows options to yield a 328 # value computed from the definitions. 329 value = 330 if !res.isDefined then 331 throw "The option `${showOption loc}' is used but not defined." 332 else if opt ? apply then 333 opt.apply res.mergedValue 334 else 335 res.mergedValue; 336 337 in opt // 338 { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value; 339 inherit (res.defsFinal') highestPrio; 340 definitions = map (def: def.value) res.defsFinal; 341 files = map (def: def.file) res.defsFinal; 342 inherit (res) isDefined; 343 }; 344 345 # Merge definitions of a value of a given type. 346 mergeDefinitions = loc: type: defs: rec { 347 defsFinal' = 348 let 349 # Process mkMerge and mkIf properties. 350 defs' = concatMap (m: 351 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value) 352 ) defs; 353 354 # Process mkOverride properties. 355 defs'' = filterOverrides' defs'; 356 357 # Sort mkOrder properties. 358 defs''' = 359 # Avoid sorting if we don't have to. 360 if any (def: def.value._type or "" == "order") defs''.values 361 then sortProperties defs''.values 362 else defs''.values; 363 in { 364 values = defs'''; 365 inherit (defs'') highestPrio; 366 }; 367 defsFinal = defsFinal'.values; 368 369 # Type-check the remaining definitions, and merge them. 370 mergedValue = foldl' (res: def: 371 if type.check def.value then res 372 else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.") 373 (type.merge loc defsFinal) defsFinal; 374 375 isDefined = defsFinal != []; 376 377 optionalValue = 378 if isDefined then { value = mergedValue; } 379 else {}; 380 }; 381 382 /* Given a config set, expand mkMerge properties, and push down the 383 other properties into the children. The result is a list of 384 config sets that do not have properties at top-level. For 385 example, 386 387 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ] 388 389 is transformed into 390 391 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ]. 392 393 This transform is the critical step that allows mkIf conditions 394 to refer to the full configuration without creating an infinite 395 recursion. 396 */ 397 pushDownProperties = cfg: 398 if cfg._type or "" == "merge" then 399 concatMap pushDownProperties cfg.contents 400 else if cfg._type or "" == "if" then 401 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) 402 else if cfg._type or "" == "override" then 403 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) 404 else # FIXME: handle mkOrder? 405 [ cfg ]; 406 407 /* Given a config value, expand mkMerge properties, and discharge 408 any mkIf conditions. That is, this is the place where mkIf 409 conditions are actually evaluated. The result is a list of 410 config values. For example, mkIf false x yields [], 411 mkIf true x yields [x], and 412 413 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ] 414 415 yields [ 1 2 ]. 416 */ 417 dischargeProperties = def: 418 if def._type or "" == "merge" then 419 concatMap dischargeProperties def.contents 420 else if def._type or "" == "if" then 421 if isBool def.condition then 422 if def.condition then 423 dischargeProperties def.content 424 else 425 [ ] 426 else 427 throw "mkIf called with a non-Boolean condition" 428 else 429 [ def ]; 430 431 /* Given a list of config values, process the mkOverride properties, 432 that is, return the values that have the highest (that is, 433 numerically lowest) priority, and strip the mkOverride 434 properties. For example, 435 436 [ { file = "/1"; value = mkOverride 10 "a"; } 437 { file = "/2"; value = mkOverride 20 "b"; } 438 { file = "/3"; value = "z"; } 439 { file = "/4"; value = mkOverride 10 "d"; } 440 ] 441 442 yields 443 444 [ { file = "/1"; value = "a"; } 445 { file = "/4"; value = "d"; } 446 ] 447 448 Note that "z" has the default priority 100. 449 */ 450 filterOverrides = defs: (filterOverrides' defs).values; 451 452 filterOverrides' = defs: 453 let 454 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPriority; 455 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs; 456 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; 457 in { 458 values = concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs; 459 inherit highestPrio; 460 }; 461 462 /* Sort a list of properties. The sort priority of a property is 463 1000 by default, but can be overridden by wrapping the property 464 using mkOrder. */ 465 sortProperties = defs: 466 let 467 strip = def: 468 if def.value._type or "" == "order" 469 then def // { value = def.value.content; inherit (def.value) priority; } 470 else def; 471 defs' = map strip defs; 472 compare = a: b: (a.priority or 1000) < (b.priority or 1000); 473 in sort compare defs'; 474 475 /* Hack for backward compatibility: convert options of type 476 optionSet to options of type submodule. FIXME: remove 477 eventually. */ 478 fixupOptionType = loc: opt: 479 let 480 options = opt.options or 481 (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}."); 482 f = tp: 483 let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet"); 484 in 485 if tp.name == "option set" || tp.name == "submodule" then 486 throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}." 487 else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options) 488 else if optionSetIn "loaOf" then types.loaOf (types.submodule options) 489 else if optionSetIn "listOf" then types.listOf (types.submodule options) 490 else if optionSetIn "nullOr" then types.nullOr (types.submodule options) 491 else tp; 492 in 493 if opt.type.getSubModules or null == null 494 then opt // { type = f (opt.type or types.unspecified); } 495 else opt // { type = opt.type.substSubModules opt.options; options = []; }; 496 497 498 /* Properties. */ 499 500 mkIf = condition: content: 501 { _type = "if"; 502 inherit condition content; 503 }; 504 505 mkAssert = assertion: message: content: 506 mkIf 507 (if assertion then true else throw "\nFailed assertion: ${message}") 508 content; 509 510 mkMerge = contents: 511 { _type = "merge"; 512 inherit contents; 513 }; 514 515 mkOverride = priority: content: 516 { _type = "override"; 517 inherit priority content; 518 }; 519 520 mkOptionDefault = mkOverride 1500; # priority of option defaults 521 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default 522 mkForce = mkOverride 50; 523 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’ 524 525 mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0); 526 527 mkFixStrictness = id; # obsolete, no-op 528 529 mkOrder = priority: content: 530 { _type = "order"; 531 inherit priority content; 532 }; 533 534 mkBefore = mkOrder 500; 535 mkAfter = mkOrder 1500; 536 537 # The default priority for things that don't have a priority specified. 538 defaultPriority = 100; 539 540 # Convenient property used to transfer all definitions and their 541 # properties from one option to another. This property is useful for 542 # renaming options, and also for including properties from another module 543 # system, including sub-modules. 544 # 545 # { config, options, ... }: 546 # 547 # { 548 # # 'bar' might not always be defined in the current module-set. 549 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {}); 550 # 551 # # 'barbaz' has to be defined in the current module-set. 552 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths; 553 # } 554 # 555 # Note, this is different than taking the value of the option and using it 556 # as a definition, as the new definition will not keep the mkOverride / 557 # mkDefault properties of the previous option. 558 # 559 mkAliasDefinitions = mkAliasAndWrapDefinitions id; 560 mkAliasAndWrapDefinitions = wrap: option: 561 mkAliasIfDef option (wrap (mkMerge option.definitions)); 562 563 # Similar to mkAliasAndWrapDefinitions but copies over the priority from the 564 # option as well. 565 # 566 # If a priority is not set, it assumes a priority of defaultPriority. 567 mkAliasAndWrapDefsWithPriority = wrap: option: 568 let 569 prio = option.highestPrio or defaultPriority; 570 defsWithPrio = map (mkOverride prio) option.definitions; 571 in mkAliasIfDef option (wrap (mkMerge defsWithPrio)); 572 573 mkAliasIfDef = option: 574 mkIf (isOption option && option.isDefined); 575 576 /* Compatibility. */ 577 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; 578 579 580 /* Return a module that causes a warning to be shown if the 581 specified option is defined. For example, 582 583 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>" 584 585 causes a warning if the user defines boot.loader.grub.bootDevice. 586 587 replacementInstructions is a string that provides instructions on 588 how to achieve the same functionality without the removed option, 589 or alternatively a reasoning why the functionality is not needed. 590 replacementInstructions SHOULD be provided! 591 */ 592 mkRemovedOptionModule = optionName: replacementInstructions: 593 { options, ... }: 594 { options = setAttrByPath optionName (mkOption { 595 visible = false; 596 }); 597 config.warnings = 598 let opt = getAttrFromPath optionName options; in 599 optional opt.isDefined '' 600 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. 601 ${replacementInstructions}''; 602 }; 603 604 /* Return a module that causes a warning to be shown if the 605 specified "from" option is defined; the defined value is however 606 forwarded to the "to" option. This can be used to rename options 607 while providing backward compatibility. For example, 608 609 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ] 610 611 forwards any definitions of boot.copyKernels to 612 boot.loader.grub.copyKernels while printing a warning. 613 614 This also copies over the priority from the aliased option to the 615 non-aliased option. 616 */ 617 mkRenamedOptionModule = from: to: doRename { 618 inherit from to; 619 visible = false; 620 warn = true; 621 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; 622 }; 623 624 /* Return a module that causes a warning to be shown if any of the "from" 625 option is defined; the defined values can be used in the "mergeFn" to set 626 the "to" value. 627 This function can be used to merge multiple options into one that has a 628 different type. 629 630 "mergeFn" takes the module "config" as a parameter and must return a value 631 of "to" option type. 632 633 mkMergedOptionModule 634 [ [ "a" "b" "c" ] 635 [ "d" "e" "f" ] ] 636 [ "x" "y" "z" ] 637 (config: 638 let value = p: getAttrFromPath p config; 639 in 640 if (value [ "a" "b" "c" ]) == true then "foo" 641 else if (value [ "d" "e" "f" ]) == true then "bar" 642 else "baz") 643 644 - options.a.b.c is a removed boolean option 645 - options.d.e.f is a removed boolean option 646 - options.x.y.z is a new str option that combines a.b.c and d.e.f 647 functionality 648 649 This show a warning if any a.b.c or d.e.f is set, and set the value of 650 x.y.z to the result of the merge function 651 */ 652 mkMergedOptionModule = from: to: mergeFn: 653 { config, options, ... }: 654 { 655 options = foldl recursiveUpdate {} (map (path: setAttrByPath path (mkOption { 656 visible = false; 657 # To use the value in mergeFn without triggering errors 658 default = "_mkMergedOptionModule"; 659 })) from); 660 661 config = { 662 warnings = filter (x: x != "") (map (f: 663 let val = getAttrFromPath f config; 664 opt = getAttrFromPath f options; 665 in 666 optionalString 667 (val != "_mkMergedOptionModule") 668 "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." 669 ) from); 670 } // setAttrByPath to (mkMerge 671 (optional 672 (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) 673 (mergeFn config))); 674 }; 675 676 /* Single "from" version of mkMergedOptionModule. 677 Return a module that causes a warning to be shown if the "from" option is 678 defined; the defined value can be used in the "mergeFn" to set the "to" 679 value. 680 This function can be used to change an option into another that has a 681 different type. 682 683 "mergeFn" takes the module "config" as a parameter and must return a value of 684 "to" option type. 685 686 mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ] 687 (config: 688 let value = getAttrFromPath [ "a" "b" "c" ] config; 689 in 690 if value > 100 then "high" 691 else "normal") 692 693 - options.a.b.c is a removed int option 694 - options.x.y.z is a new str option that supersedes a.b.c 695 696 This show a warning if a.b.c is set, and set the value of x.y.z to the 697 result of the change function 698 */ 699 mkChangedOptionModule = from: to: changeFn: 700 mkMergedOptionModule [ from ] to changeFn; 701 702 /* Like mkRenamedOptionModule, but doesn't show a warning. */ 703 mkAliasOptionModule = from: to: doRename { 704 inherit from to; 705 visible = true; 706 warn = false; 707 use = id; 708 }; 709 710 doRename = { from, to, visible, warn, use, withPriority ? true }: 711 { config, options, ... }: 712 let 713 fromOpt = getAttrFromPath from options; 714 toOf = attrByPath to 715 (abort "Renaming error: option `${showOption to}' does not exist."); 716 in 717 { 718 options = setAttrByPath from (mkOption { 719 inherit visible; 720 description = "Alias of <option>${showOption to}</option>."; 721 apply = x: use (toOf config); 722 }); 723 config = mkMerge [ 724 { 725 warnings = optional (warn && fromOpt.isDefined) 726 "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; 727 } 728 (if withPriority 729 then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt 730 else mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt) 731 ]; 732 }; 733 734}