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}