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}