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 = import ./.; } // 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 = 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 listToAttrs (map (name: {
196 # We're descending into attribute ‘name’.
197 inherit name;
198 value =
199 let
200 loc = prefix ++ [name];
201 # Get all submodules that declare ‘name’.
202 decls = concatMap (m:
203 if m.options ? ${name}
204 then [ { inherit (m) file; options = m.options.${name}; } ]
205 else []
206 ) options;
207 # Get all submodules that define ‘name’.
208 defns = concatMap (m:
209 if m.config ? ${name}
210 then map (config: { inherit (m) file; inherit config; })
211 (pushDownProperties m.config.${name})
212 else []
213 ) configs;
214 nrOptions = count (m: isOption m.options) decls;
215 # Extract the definitions for this loc
216 defns' = map (m: { inherit (m) file; value = m.config.${name}; })
217 (filter (m: m.config ? ${name}) configs);
218 in
219 if nrOptions == length decls then
220 let opt = fixupOptionType loc (mergeOptionDecls loc decls);
221 in evalOptionValue loc opt defns'
222 else if nrOptions != 0 then
223 let
224 firstOption = findFirst (m: isOption m.options) "" decls;
225 firstNonOption = findFirst (m: !isOption m.options) "" decls;
226 in
227 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
228 else
229 mergeModules' loc decls defns;
230 }) (concatMap (m: attrNames m.options) options))
231 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
232
233 /* Merge multiple option declarations into a single declaration. In
234 general, there should be only one declaration of each option.
235 The exception is the ‘options’ attribute, which specifies
236 sub-options. These can be specified multiple times to allow one
237 module to add sub-options to an option declared somewhere else
238 (e.g. multiple modules define sub-options for ‘fileSystems’).
239
240 'loc' is the list of attribute names where the option is located.
241
242 'opts' is a list of modules. Each module has an options attribute which
243 correspond to the definition of 'loc' in 'opt.file'. */
244 mergeOptionDecls = loc: opts:
245 foldl' (res: opt:
246 let t = res.type;
247 t' = opt.options.type;
248 mergedType = t.typeMerge t'.functor;
249 typesMergeable = mergedType != null;
250 typeSet = if (bothHave "type") && typesMergeable
251 then { type = mergedType; }
252 else {};
253 bothHave = k: opt.options ? ${k} && res ? ${k};
254 in
255 if bothHave "default" ||
256 bothHave "example" ||
257 bothHave "description" ||
258 bothHave "apply" ||
259 (bothHave "type" && (! typesMergeable))
260 then
261 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
262 else
263 let
264 /* Add the modules of the current option to the list of modules
265 already collected. The options attribute except either a list of
266 submodules or a submodule. For each submodule, we add the file of the
267 current option declaration as the file use for the submodule. If the
268 submodule defines any filename, then we ignore the enclosing option file. */
269 options' = toList opt.options.options;
270 coerceOption = file: opt:
271 if isFunction opt then packSubmodule file opt
272 else packSubmodule file { options = opt; };
273 getSubModules = opt.options.type.getSubModules or null;
274 submodules =
275 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options
276 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options
277 else res.options;
278 in opt.options // res //
279 { declarations = res.declarations ++ [opt.file];
280 options = submodules;
281 } // typeSet
282 ) { inherit loc; declarations = []; options = []; } opts;
283
284 /* Merge all the definitions of an option to produce the final
285 config value. */
286 evalOptionValue = loc: opt: defs:
287 let
288 # Add in the default value for this option, if any.
289 defs' =
290 (optional (opt ? default)
291 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
292
293 # Handle properties, check types, and merge everything together.
294 res =
295 if opt.readOnly or false && length defs' > 1 then
296 throw "The option `${showOption loc}' is read-only, but it's set multiple times."
297 else
298 mergeDefinitions loc opt.type defs';
299
300 # Check whether the option is defined, and apply the ‘apply’
301 # function to the merged value. This allows options to yield a
302 # value computed from the definitions.
303 value =
304 if !res.isDefined then
305 throw "The option `${showOption loc}' is used but not defined."
306 else if opt ? apply then
307 opt.apply res.mergedValue
308 else
309 res.mergedValue;
310
311 in opt //
312 { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
313 definitions = map (def: def.value) res.defsFinal;
314 files = map (def: def.file) res.defsFinal;
315 inherit (res) isDefined;
316 };
317
318 # Merge definitions of a value of a given type.
319 mergeDefinitions = loc: type: defs: rec {
320 defsFinal =
321 let
322 # Process mkMerge and mkIf properties.
323 defs' = concatMap (m:
324 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value)
325 ) defs;
326
327 # Process mkOverride properties.
328 defs'' = filterOverrides defs';
329
330 # Sort mkOrder properties.
331 defs''' =
332 # Avoid sorting if we don't have to.
333 if any (def: def.value._type or "" == "order") defs''
334 then sortProperties defs''
335 else defs'';
336 in defs''';
337
338 # Type-check the remaining definitions, and merge them.
339 mergedValue = foldl' (res: def:
340 if type.check def.value then res
341 else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.")
342 (type.merge loc defsFinal) defsFinal;
343
344 isDefined = defsFinal != [];
345
346 optionalValue =
347 if isDefined then { value = mergedValue; }
348 else {};
349 };
350
351 /* Given a config set, expand mkMerge properties, and push down the
352 other properties into the children. The result is a list of
353 config sets that do not have properties at top-level. For
354 example,
355
356 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
357
358 is transformed into
359
360 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
361
362 This transform is the critical step that allows mkIf conditions
363 to refer to the full configuration without creating an infinite
364 recursion.
365 */
366 pushDownProperties = cfg:
367 if cfg._type or "" == "merge" then
368 concatMap pushDownProperties cfg.contents
369 else if cfg._type or "" == "if" then
370 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
371 else if cfg._type or "" == "override" then
372 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
373 else # FIXME: handle mkOrder?
374 [ cfg ];
375
376 /* Given a config value, expand mkMerge properties, and discharge
377 any mkIf conditions. That is, this is the place where mkIf
378 conditions are actually evaluated. The result is a list of
379 config values. For example, ‘mkIf false x’ yields ‘[]’,
380 ‘mkIf true x’ yields ‘[x]’, and
381
382 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
383
384 yields ‘[ 1 2 ]’.
385 */
386 dischargeProperties = def:
387 if def._type or "" == "merge" then
388 concatMap dischargeProperties def.contents
389 else if def._type or "" == "if" then
390 if isBool def.condition then
391 if def.condition then
392 dischargeProperties def.content
393 else
394 [ ]
395 else
396 throw "‘mkIf’ called with a non-Boolean condition"
397 else
398 [ def ];
399
400 /* Given a list of config values, process the mkOverride properties,
401 that is, return the values that have the highest (that is,
402 numerically lowest) priority, and strip the mkOverride
403 properties. For example,
404
405 [ { file = "/1"; value = mkOverride 10 "a"; }
406 { file = "/2"; value = mkOverride 20 "b"; }
407 { file = "/3"; value = "z"; }
408 { file = "/4"; value = mkOverride 10 "d"; }
409 ]
410
411 yields
412
413 [ { file = "/1"; value = "a"; }
414 { file = "/4"; value = "d"; }
415 ]
416
417 Note that "z" has the default priority 100.
418 */
419 filterOverrides = defs:
420 let
421 defaultPrio = 100;
422 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio;
423 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
424 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
425 in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
426
427 /* Sort a list of properties. The sort priority of a property is
428 1000 by default, but can be overridden by wrapping the property
429 using mkOrder. */
430 sortProperties = defs:
431 let
432 strip = def:
433 if def.value._type or "" == "order"
434 then def // { value = def.value.content; inherit (def.value) priority; }
435 else def;
436 defs' = map strip defs;
437 compare = a: b: (a.priority or 1000) < (b.priority or 1000);
438 in sort compare defs';
439
440 /* Hack for backward compatibility: convert options of type
441 optionSet to options of type submodule. FIXME: remove
442 eventually. */
443 fixupOptionType = loc: opt:
444 let
445 options = opt.options or
446 (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
447 f = tp:
448 let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
449 in
450 if tp.name == "option set" || tp.name == "submodule" then
451 throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
452 else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
453 else if optionSetIn "loaOf" then types.loaOf (types.submodule options)
454 else if optionSetIn "listOf" then types.listOf (types.submodule options)
455 else if optionSetIn "nullOr" then types.nullOr (types.submodule options)
456 else tp;
457 in
458 if opt.type.getSubModules or null == null
459 then opt // { type = f (opt.type or types.unspecified); }
460 else opt // { type = opt.type.substSubModules opt.options; options = []; };
461
462
463 /* Properties. */
464
465 mkIf = condition: content:
466 { _type = "if";
467 inherit condition content;
468 };
469
470 mkAssert = assertion: message: content:
471 mkIf
472 (if assertion then true else throw "\nFailed assertion: ${message}")
473 content;
474
475 mkMerge = contents:
476 { _type = "merge";
477 inherit contents;
478 };
479
480 mkOverride = priority: content:
481 { _type = "override";
482 inherit priority content;
483 };
484
485 mkOptionDefault = mkOverride 1001; # priority of option defaults
486 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
487 mkForce = mkOverride 50;
488 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
489
490 mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0);
491
492 mkFixStrictness = id; # obsolete, no-op
493
494 mkOrder = priority: content:
495 { _type = "order";
496 inherit priority content;
497 };
498
499 mkBefore = mkOrder 500;
500 mkAfter = mkOrder 1500;
501
502
503 # Convenient property used to transfer all definitions and their
504 # properties from one option to another. This property is useful for
505 # renaming options, and also for including properties from another module
506 # system, including sub-modules.
507 #
508 # { config, options, ... }:
509 #
510 # {
511 # # 'bar' might not always be defined in the current module-set.
512 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
513 #
514 # # 'barbaz' has to be defined in the current module-set.
515 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
516 # }
517 #
518 # Note, this is different than taking the value of the option and using it
519 # as a definition, as the new definition will not keep the mkOverride /
520 # mkDefault properties of the previous option.
521 #
522 mkAliasDefinitions = mkAliasAndWrapDefinitions id;
523 mkAliasAndWrapDefinitions = wrap: option:
524 mkMerge
525 (optional (isOption option && option.isDefined)
526 (wrap (mkMerge option.definitions)));
527
528
529 /* Compatibility. */
530 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
531
532
533 /* Return a module that causes a warning to be shown if the
534 specified option is defined. For example,
535
536 mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
537
538 causes a warning if the user defines boot.loader.grub.bootDevice.
539
540 replacementInstructions is a string that provides instructions on
541 how to achieve the same functionality without the removed option,
542 or alternatively a reasoning why the functionality is not needed.
543 replacementInstructions SHOULD be provided!
544 */
545 mkRemovedOptionModule = optionName: replacementInstructions:
546 { options, ... }:
547 { options = setAttrByPath optionName (mkOption {
548 visible = false;
549 });
550 config.warnings =
551 let opt = getAttrFromPath optionName options; in
552 optional opt.isDefined ''
553 The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
554 ${replacementInstructions}'';
555 };
556
557 /* Return a module that causes a warning to be shown if the
558 specified "from" option is defined; the defined value is however
559 forwarded to the "to" option. This can be used to rename options
560 while providing backward compatibility. For example,
561
562 mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
563
564 forwards any definitions of boot.copyKernels to
565 boot.loader.grub.copyKernels while printing a warning.
566 */
567 mkRenamedOptionModule = from: to: doRename {
568 inherit from to;
569 visible = false;
570 warn = true;
571 use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
572 };
573
574 /* Return a module that causes a warning to be shown if any of the "from"
575 option is defined; the defined values can be used in the "mergeFn" to set
576 the "to" value.
577 This function can be used to merge multiple options into one that has a
578 different type.
579
580 "mergeFn" takes the module "config" as a parameter and must return a value
581 of "to" option type.
582
583 mkMergedOptionModule
584 [ [ "a" "b" "c" ]
585 [ "d" "e" "f" ] ]
586 [ "x" "y" "z" ]
587 (config:
588 let value = p: getAttrFromPath p config;
589 in
590 if (value [ "a" "b" "c" ]) == true then "foo"
591 else if (value [ "d" "e" "f" ]) == true then "bar"
592 else "baz")
593
594 - options.a.b.c is a removed boolean option
595 - options.d.e.f is a removed boolean option
596 - options.x.y.z is a new str option that combines a.b.c and d.e.f
597 functionality
598
599 This show a warning if any a.b.c or d.e.f is set, and set the value of
600 x.y.z to the result of the merge function
601 */
602 mkMergedOptionModule = from: to: mergeFn:
603 { config, options, ... }:
604 {
605 options = foldl recursiveUpdate {} (map (path: setAttrByPath path (mkOption {
606 visible = false;
607 # To use the value in mergeFn without triggering errors
608 default = "_mkMergedOptionModule";
609 })) from);
610
611 config = {
612 warnings = filter (x: x != "") (map (f:
613 let val = getAttrFromPath f config;
614 opt = getAttrFromPath f options;
615 in
616 optionalString
617 (val != "_mkMergedOptionModule")
618 "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."
619 ) from);
620 } // setAttrByPath to (mkMerge
621 (optional
622 (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
623 (mergeFn config)));
624 };
625
626 /* Single "from" version of mkMergedOptionModule.
627 Return a module that causes a warning to be shown if the "from" option is
628 defined; the defined value can be used in the "mergeFn" to set the "to"
629 value.
630 This function can be used to change an option into another that has a
631 different type.
632
633 "mergeFn" takes the module "config" as a parameter and must return a value of
634 "to" option type.
635
636 mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ]
637 (config:
638 let value = getAttrFromPath [ "a" "b" "c" ] config;
639 in
640 if value > 100 then "high"
641 else "normal")
642
643 - options.a.b.c is a removed int option
644 - options.x.y.z is a new str option that supersedes a.b.c
645
646 This show a warning if a.b.c is set, and set the value of x.y.z to the
647 result of the change function
648 */
649 mkChangedOptionModule = from: to: changeFn:
650 mkMergedOptionModule [ from ] to changeFn;
651
652 /* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */
653 mkAliasOptionModule = from: to: doRename {
654 inherit from to;
655 visible = true;
656 warn = false;
657 use = id;
658 };
659
660 doRename = { from, to, visible, warn, use }:
661 let
662 toOf = attrByPath to
663 (abort "Renaming error: option `${showOption to}' does not exists.");
664 in
665 { config, options, ... }:
666 { options = setAttrByPath from (mkOption {
667 description = "Alias of <option>${showOption to}</option>.";
668 apply = x: use (toOf config);
669 });
670 config = {
671 warnings =
672 let opt = getAttrFromPath from options; in
673 optional (warn && opt.isDefined)
674 "The option `${showOption from}' defined in ${showFiles opt.files} has been renamed to `${showOption to}'.";
675 } // setAttrByPath to (mkAliasDefinitions (getAttrFromPath from options));
676 };
677
678}