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