at 18.09-beta 28 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: foldl' (acc: module: 218 foldl' (inner: name: 219 inner // { ${name} = (acc.${name} or []) ++ (f module module.${attr}.${name}); } 220 ) acc (attrNames module.${attr}) 221 ) {} modules; 222 # an attrset 'name' => list of submodules that declare ‘name’. 223 declsByName = byName "options" 224 (module: option: [{ inherit (module) file; options = option; }]) 225 options; 226 # an attrset 'name' => list of submodules that define ‘name’. 227 defnsByName = byName "config" (module: value: 228 map (config: { inherit (module) file; inherit config; }) (pushDownProperties value) 229 ) configs; 230 # extract the definitions for each loc 231 defnsByName' = byName "config" 232 (module: value: [{ inherit (module) file; inherit value; }]) 233 configs; 234 in 235 (flip mapAttrs declsByName (name: decls: 236 # We're descending into attribute ‘name’. 237 let 238 loc = prefix ++ [name]; 239 defns = defnsByName.${name} or []; 240 defns' = defnsByName'.${name} or []; 241 nrOptions = count (m: isOption m.options) decls; 242 in 243 if nrOptions == length decls then 244 let opt = fixupOptionType loc (mergeOptionDecls loc decls); 245 in evalOptionValue loc opt defns' 246 else if nrOptions != 0 then 247 let 248 firstOption = findFirst (m: isOption m.options) "" decls; 249 firstNonOption = findFirst (m: !isOption m.options) "" decls; 250 in 251 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." 252 else 253 mergeModules' loc decls defns 254 )) 255 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; 256 257 /* Merge multiple option declarations into a single declaration. In 258 general, there should be only one declaration of each option. 259 The exception is the options attribute, which specifies 260 sub-options. These can be specified multiple times to allow one 261 module to add sub-options to an option declared somewhere else 262 (e.g. multiple modules define sub-options for fileSystems). 263 264 'loc' is the list of attribute names where the option is located. 265 266 'opts' is a list of modules. Each module has an options attribute which 267 correspond to the definition of 'loc' in 'opt.file'. */ 268 mergeOptionDecls = loc: opts: 269 foldl' (res: opt: 270 let t = res.type; 271 t' = opt.options.type; 272 mergedType = t.typeMerge t'.functor; 273 typesMergeable = mergedType != null; 274 typeSet = if (bothHave "type") && typesMergeable 275 then { type = mergedType; } 276 else {}; 277 bothHave = k: opt.options ? ${k} && res ? ${k}; 278 in 279 if bothHave "default" || 280 bothHave "example" || 281 bothHave "description" || 282 bothHave "apply" || 283 (bothHave "type" && (! typesMergeable)) 284 then 285 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}." 286 else 287 let 288 /* Add the modules of the current option to the list of modules 289 already collected. The options attribute except either a list of 290 submodules or a submodule. For each submodule, we add the file of the 291 current option declaration as the file use for the submodule. If the 292 submodule defines any filename, then we ignore the enclosing option file. */ 293 options' = toList opt.options.options; 294 coerceOption = file: opt: 295 if isFunction opt then packSubmodule file opt 296 else packSubmodule file { options = opt; }; 297 getSubModules = opt.options.type.getSubModules or null; 298 submodules = 299 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options 300 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options 301 else res.options; 302 in opt.options // res // 303 { declarations = res.declarations ++ [opt.file]; 304 options = submodules; 305 } // typeSet 306 ) { inherit loc; declarations = []; options = []; } opts; 307 308 /* Merge all the definitions of an option to produce the final 309 config value. */ 310 evalOptionValue = loc: opt: defs: 311 let 312 # Add in the default value for this option, if any. 313 defs' = 314 (optional (opt ? default) 315 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; 316 317 # Handle properties, check types, and merge everything together. 318 res = 319 if opt.readOnly or false && length defs' > 1 then 320 throw "The option `${showOption loc}' is read-only, but it's set multiple times." 321 else 322 mergeDefinitions loc opt.type defs'; 323 324 # Check whether the option is defined, and apply the ‘apply’ 325 # function to the merged value. This allows options to yield a 326 # value computed from the definitions. 327 value = 328 if !res.isDefined then 329 throw "The option `${showOption loc}' is used but not defined." 330 else if opt ? apply then 331 opt.apply res.mergedValue 332 else 333 res.mergedValue; 334 335 in opt // 336 { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value; 337 inherit (res.defsFinal') highestPrio; 338 definitions = map (def: def.value) res.defsFinal; 339 files = map (def: def.file) res.defsFinal; 340 inherit (res) isDefined; 341 }; 342 343 # Merge definitions of a value of a given type. 344 mergeDefinitions = loc: type: defs: rec { 345 defsFinal' = 346 let 347 # Process mkMerge and mkIf properties. 348 defs' = concatMap (m: 349 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value) 350 ) defs; 351 352 # Process mkOverride properties. 353 defs'' = filterOverrides' defs'; 354 355 # Sort mkOrder properties. 356 defs''' = 357 # Avoid sorting if we don't have to. 358 if any (def: def.value._type or "" == "order") defs''.values 359 then sortProperties defs''.values 360 else defs''.values; 361 in { 362 values = defs'''; 363 inherit (defs'') highestPrio; 364 }; 365 366 defsFinal = defsFinal'.values; 367 368 # Type-check the remaining definitions, and merge them. 369 mergedValue = foldl' (res: def: 370 if type.check def.value then res 371 else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.") 372 (type.merge loc defsFinal) defsFinal; 373 374 isDefined = defsFinal != []; 375 376 optionalValue = 377 if isDefined then { value = mergedValue; } 378 else {}; 379 }; 380 381 /* Given a config set, expand mkMerge properties, and push down the 382 other properties into the children. The result is a list of 383 config sets that do not have properties at top-level. For 384 example, 385 386 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ] 387 388 is transformed into 389 390 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ]. 391 392 This transform is the critical step that allows mkIf conditions 393 to refer to the full configuration without creating an infinite 394 recursion. 395 */ 396 pushDownProperties = cfg: 397 if cfg._type or "" == "merge" then 398 concatMap pushDownProperties cfg.contents 399 else if cfg._type or "" == "if" then 400 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) 401 else if cfg._type or "" == "override" then 402 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) 403 else # FIXME: handle mkOrder? 404 [ cfg ]; 405 406 /* Given a config value, expand mkMerge properties, and discharge 407 any mkIf conditions. That is, this is the place where mkIf 408 conditions are actually evaluated. The result is a list of 409 config values. For example, mkIf false x yields [], 410 mkIf true x yields [x], and 411 412 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ] 413 414 yields [ 1 2 ]. 415 */ 416 dischargeProperties = def: 417 if def._type or "" == "merge" then 418 concatMap dischargeProperties def.contents 419 else if def._type or "" == "if" then 420 if isBool def.condition then 421 if def.condition then 422 dischargeProperties def.content 423 else 424 [ ] 425 else 426 throw "mkIf called with a non-Boolean condition" 427 else 428 [ def ]; 429 430 /* Given a list of config values, process the mkOverride properties, 431 that is, return the values that have the highest (that is, 432 numerically lowest) priority, and strip the mkOverride 433 properties. For example, 434 435 [ { file = "/1"; value = mkOverride 10 "a"; } 436 { file = "/2"; value = mkOverride 20 "b"; } 437 { file = "/3"; value = "z"; } 438 { file = "/4"; value = mkOverride 10 "d"; } 439 ] 440 441 yields 442 443 [ { file = "/1"; value = "a"; } 444 { file = "/4"; value = "d"; } 445 ] 446 447 Note that "z" has the default priority 100. 448 */ 449 filterOverrides = defs: (filterOverrides' defs).values; 450 451 filterOverrides' = defs: 452 let 453 defaultPrio = 100; 454 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio; 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 538 # Convenient property used to transfer all definitions and their 539 # properties from one option to another. This property is useful for 540 # renaming options, and also for including properties from another module 541 # system, including sub-modules. 542 # 543 # { config, options, ... }: 544 # 545 # { 546 # # 'bar' might not always be defined in the current module-set. 547 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {}); 548 # 549 # # 'barbaz' has to be defined in the current module-set. 550 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths; 551 # } 552 # 553 # Note, this is different than taking the value of the option and using it 554 # as a definition, as the new definition will not keep the mkOverride / 555 # mkDefault properties of the previous option. 556 # 557 mkAliasDefinitions = mkAliasAndWrapDefinitions id; 558 mkAliasAndWrapDefinitions = wrap: option: 559 mkIf (isOption option && option.isDefined) (wrap (mkMerge option.definitions)); 560 561 562 /* Compatibility. */ 563 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; 564 565 566 /* Return a module that causes a warning to be shown if the 567 specified option is defined. For example, 568 569 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>" 570 571 causes a warning if the user defines boot.loader.grub.bootDevice. 572 573 replacementInstructions is a string that provides instructions on 574 how to achieve the same functionality without the removed option, 575 or alternatively a reasoning why the functionality is not needed. 576 replacementInstructions SHOULD be provided! 577 */ 578 mkRemovedOptionModule = optionName: replacementInstructions: 579 { options, ... }: 580 { options = setAttrByPath optionName (mkOption { 581 visible = false; 582 }); 583 config.warnings = 584 let opt = getAttrFromPath optionName options; in 585 optional opt.isDefined '' 586 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. 587 ${replacementInstructions}''; 588 }; 589 590 /* Return a module that causes a warning to be shown if the 591 specified "from" option is defined; the defined value is however 592 forwarded to the "to" option. This can be used to rename options 593 while providing backward compatibility. For example, 594 595 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ] 596 597 forwards any definitions of boot.copyKernels to 598 boot.loader.grub.copyKernels while printing a warning. 599 */ 600 mkRenamedOptionModule = from: to: doRename { 601 inherit from to; 602 visible = false; 603 warn = true; 604 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; 605 }; 606 607 /* Return a module that causes a warning to be shown if any of the "from" 608 option is defined; the defined values can be used in the "mergeFn" to set 609 the "to" value. 610 This function can be used to merge multiple options into one that has a 611 different type. 612 613 "mergeFn" takes the module "config" as a parameter and must return a value 614 of "to" option type. 615 616 mkMergedOptionModule 617 [ [ "a" "b" "c" ] 618 [ "d" "e" "f" ] ] 619 [ "x" "y" "z" ] 620 (config: 621 let value = p: getAttrFromPath p config; 622 in 623 if (value [ "a" "b" "c" ]) == true then "foo" 624 else if (value [ "d" "e" "f" ]) == true then "bar" 625 else "baz") 626 627 - options.a.b.c is a removed boolean option 628 - options.d.e.f is a removed boolean option 629 - options.x.y.z is a new str option that combines a.b.c and d.e.f 630 functionality 631 632 This show a warning if any a.b.c or d.e.f is set, and set the value of 633 x.y.z to the result of the merge function 634 */ 635 mkMergedOptionModule = from: to: mergeFn: 636 { config, options, ... }: 637 { 638 options = foldl recursiveUpdate {} (map (path: setAttrByPath path (mkOption { 639 visible = false; 640 # To use the value in mergeFn without triggering errors 641 default = "_mkMergedOptionModule"; 642 })) from); 643 644 config = { 645 warnings = filter (x: x != "") (map (f: 646 let val = getAttrFromPath f config; 647 opt = getAttrFromPath f options; 648 in 649 optionalString 650 (val != "_mkMergedOptionModule") 651 "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." 652 ) from); 653 } // setAttrByPath to (mkMerge 654 (optional 655 (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) 656 (mergeFn config))); 657 }; 658 659 /* Single "from" version of mkMergedOptionModule. 660 Return a module that causes a warning to be shown if the "from" option is 661 defined; the defined value can be used in the "mergeFn" to set the "to" 662 value. 663 This function can be used to change an option into another that has a 664 different type. 665 666 "mergeFn" takes the module "config" as a parameter and must return a value of 667 "to" option type. 668 669 mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ] 670 (config: 671 let value = getAttrFromPath [ "a" "b" "c" ] config; 672 in 673 if value > 100 then "high" 674 else "normal") 675 676 - options.a.b.c is a removed int option 677 - options.x.y.z is a new str option that supersedes a.b.c 678 679 This show a warning if a.b.c is set, and set the value of x.y.z to the 680 result of the change function 681 */ 682 mkChangedOptionModule = from: to: changeFn: 683 mkMergedOptionModule [ from ] to changeFn; 684 685 /* Like mkRenamedOptionModule, but doesn't show a warning. */ 686 mkAliasOptionModule = from: to: doRename { 687 inherit from to; 688 visible = true; 689 warn = false; 690 use = id; 691 }; 692 693 doRename = { from, to, visible, warn, use }: 694 { config, options, ... }: 695 let 696 fromOpt = getAttrFromPath from options; 697 toOf = attrByPath to 698 (abort "Renaming error: option `${showOption to}' does not exist."); 699 in 700 { 701 options = setAttrByPath from (mkOption { 702 inherit visible; 703 description = "Alias of <option>${showOption to}</option>."; 704 apply = x: use (toOf config); 705 }); 706 config = mkMerge [ 707 { 708 warnings = optional (warn && fromOpt.isDefined) 709 "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; 710 } 711 (mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt) 712 ]; 713 }; 714 715}