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