1/**
2 Module System option handling.
3*/
4{ lib }:
5
6let
7 inherit (lib)
8 all
9 collect
10 concatLists
11 concatMap
12 concatMapStringsSep
13 filter
14 foldl'
15 head
16 tail
17 isAttrs
18 isBool
19 isDerivation
20 isFunction
21 isInt
22 isList
23 isString
24 length
25 mapAttrs
26 optional
27 optionals
28 take
29 ;
30 inherit (lib.attrsets)
31 attrByPath
32 optionalAttrs
33 showAttrPath
34 ;
35 inherit (lib.strings)
36 concatMapStrings
37 concatStringsSep
38 ;
39 inherit (lib.types)
40 mkOptionType
41 ;
42 inherit (lib.lists)
43 last
44 toList
45 ;
46 prioritySuggestion = ''
47 Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions.
48 '';
49in
50rec {
51
52 /**
53 Returns true when the given argument `a` is an option
54
55 # Inputs
56
57 `a`
58 : Any value to check whether it is an option
59
60 # Examples
61 :::{.example}
62 ## `lib.options.isOption` usage example
63
64 ```nix
65 isOption 1 // => false
66 isOption (mkOption {}) // => true
67 ```
68
69 :::
70
71 # Type
72
73 ```
74 isOption :: a -> Bool
75 ```
76 */
77 isOption = lib.isType "option";
78
79 /**
80 Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
81
82 # Inputs
83
84 Structured attribute set
85 : Attribute set containing none or some of the following attributes.
86
87 `default`
88 : Optional default value used when no definition is given in the configuration.
89
90 `defaultText`
91 : Substitute for documenting the `default`, if evaluating the default value during documentation rendering is not possible.
92 : Can be any nix value that evaluates.
93 : Usage with `lib.literalMD` or `lib.literalExpression` is supported
94
95 `example`
96 : Optional example value used in the manual.
97 : Can be any nix value that evaluates.
98 : Usage with `lib.literalMD` or `lib.literalExpression` is supported
99
100 `description`
101 : Optional string describing the option. This is required if option documentation is generated.
102
103 `relatedPackages`
104 : Optional related packages used in the manual (see `genRelatedPackages` in `../nixos/lib/make-options-doc/default.nix`).
105
106 `type`
107 : Optional option type, providing type-checking and value merging.
108
109 `apply`
110 : Optional function that converts the option value to something else.
111
112 `internal`
113 : Optional boolean indicating whether the option is for NixOS developers only.
114
115 `visible`
116 : Optional, whether the option and/or sub-options show up in the manual.
117 Use false to hide the option and any sub-options from submodules.
118 Use "shallow" to hide only sub-options.
119 Use "transparent" to hide this option, but not its sub-options.
120 Default: true.
121
122 `readOnly`
123 : Optional boolean indicating whether the option can be set only once.
124
125 `...` (any other attribute)
126 : Any other attribute is passed through to the resulting option attribute set.
127
128 # Examples
129 :::{.example}
130 ## `lib.options.mkOption` usage example
131
132 ```nix
133 mkOption { } // => { _type = "option"; }
134 mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; }
135 ```
136
137 :::
138 */
139 mkOption =
140 {
141 default ? null,
142 defaultText ? null,
143 example ? null,
144 description ? null,
145 relatedPackages ? null,
146 type ? null,
147 apply ? null,
148 internal ? null,
149 visible ? null,
150 readOnly ? null,
151 }@attrs:
152 attrs // { _type = "option"; };
153
154 /**
155 Creates an option declaration with a default value of ´false´, and can be defined to ´true´.
156
157 # Inputs
158
159 `name`
160
161 : Name for the created option
162
163 # Examples
164 :::{.example}
165 ## `lib.options.mkEnableOption` usage example
166
167 ```nix
168 # module
169 let
170 eval = lib.evalModules {
171 modules = [
172 {
173 options.foo.enable = mkEnableOption "foo";
174
175 config.foo.enable = true;
176 }
177 ];
178 };
179 in
180 eval.config
181 => { foo.enable = true; }
182 ```
183
184 :::
185 */
186 mkEnableOption =
187 name:
188 mkOption {
189 default = false;
190 example = true;
191 description = "Whether to enable ${name}.";
192 type = lib.types.bool;
193 };
194
195 /**
196 Creates an Option attribute set for an option that specifies the
197 package a module should use for some purpose.
198
199 The package is specified in the third argument under `default` as a list of strings
200 representing its attribute path in nixpkgs (or another package set).
201 Because of this, you need to pass nixpkgs itself (usually `pkgs` in a module;
202 alternatively to nixpkgs itself, another package set) as the first argument.
203
204 If you pass another package set you should set the `pkgsText` option.
205 This option is used to display the expression for the package set. It is `"pkgs"` by default.
206 If your expression is complex you should parenthesize it, as the `pkgsText` argument
207 is usually immediately followed by an attribute lookup (`.`).
208
209 The second argument may be either a string or a list of strings.
210 It provides the display name of the package in the description of the generated option
211 (using only the last element if the passed value is a list)
212 and serves as the fallback value for the `default` argument.
213
214 To include extra information in the description, pass `extraDescription` to
215 append arbitrary text to the generated description.
216
217 You can also pass an `example` value, either a literal string or an attribute path.
218
219 The `default` argument can be omitted if the provided name is
220 an attribute of pkgs (if `name` is a string) or a valid attribute path in pkgs (if `name` is a list).
221 You can also set `default` to just a string in which case it is interpreted as an attribute name
222 (a singleton attribute path, if you will).
223
224 If you wish to explicitly provide no default, pass `null` as `default`.
225
226 If you want users to be able to set no package, pass `nullable = true`.
227 In this mode a `default = null` will not be interpreted as no default and is interpreted literally.
228
229 # Inputs
230
231 `pkgs`
232
233 : Package set (an instantiation of nixpkgs such as pkgs in modules or another package set)
234
235 `name`
236
237 : Name for the package, shown in option description
238
239 Structured function argument
240 : Attribute set containing the following attributes.
241
242 `nullable`
243 : Optional whether the package can be null, for example to disable installing a package altogether. Default: `false`
244
245 `default`
246 : Optional attribute path where the default package is located. Default: `name`
247 If omitted will be copied from `name`
248
249 `example`
250 : Optional string or an attribute path to use as an example. Default: `null`
251
252 `extraDescription`
253 : Optional additional text to include in the option description. Default: `""`
254
255 `pkgsText`
256 : Optional representation of the package set passed as pkgs. Default: `"pkgs"`
257
258 # Type
259
260 ```
261 mkPackageOption :: pkgs -> (string|[string]) -> { nullable? :: bool, default? :: string|[string], example? :: null|string|[string], extraDescription? :: string, pkgsText? :: string } -> option
262 ```
263
264 # Examples
265 :::{.example}
266 ## `lib.options.mkPackageOption` usage example
267
268 ```nix
269 mkPackageOption pkgs "hello" { }
270 => { ...; default = pkgs.hello; defaultText = literalExpression "pkgs.hello"; description = "The hello package to use."; type = package; }
271
272 mkPackageOption pkgs "GHC" {
273 default = [ "ghc" ];
274 example = "pkgs.haskellPackages.ghc.withPackages (hkgs: [ hkgs.primes ])";
275 }
276 => { ...; default = pkgs.ghc; defaultText = literalExpression "pkgs.ghc"; description = "The GHC package to use."; example = literalExpression "pkgs.haskellPackages.ghc.withPackages (hkgs: [ hkgs.primes ])"; type = package; }
277
278 mkPackageOption pkgs [ "python3Packages" "pytorch" ] {
279 extraDescription = "This is an example and doesn't actually do anything.";
280 }
281 => { ...; default = pkgs.python3Packages.pytorch; defaultText = literalExpression "pkgs.python3Packages.pytorch"; description = "The pytorch package to use. This is an example and doesn't actually do anything."; type = package; }
282
283 mkPackageOption pkgs "nushell" {
284 nullable = true;
285 }
286 => { ...; default = pkgs.nushell; defaultText = literalExpression "pkgs.nushell"; description = "The nushell package to use."; type = nullOr package; }
287
288 mkPackageOption pkgs "coreutils" {
289 default = null;
290 }
291 => { ...; description = "The coreutils package to use."; type = package; }
292
293 mkPackageOption pkgs "dbus" {
294 nullable = true;
295 default = null;
296 }
297 => { ...; default = null; description = "The dbus package to use."; type = nullOr package; }
298
299 mkPackageOption pkgs.javaPackages "OpenJFX" {
300 default = "openjfx20";
301 pkgsText = "pkgs.javaPackages";
302 }
303 => { ...; default = pkgs.javaPackages.openjfx20; defaultText = literalExpression "pkgs.javaPackages.openjfx20"; description = "The OpenJFX package to use."; type = package; }
304 ```
305
306 :::
307 */
308 mkPackageOption =
309 pkgs: name:
310 {
311 nullable ? false,
312 default ? name,
313 example ? null,
314 extraDescription ? "",
315 pkgsText ? "pkgs",
316 }:
317 let
318 name' = if isList name then last name else name;
319 default' = toList default;
320 defaultText = showAttrPath default';
321 defaultValue = attrByPath default' (throw "${defaultText} cannot be found in ${pkgsText}") pkgs;
322 defaults =
323 if default != null then
324 {
325 default = defaultValue;
326 defaultText = literalExpression "${pkgsText}.${defaultText}";
327 }
328 else
329 optionalAttrs nullable {
330 default = null;
331 };
332 in
333 mkOption (
334 defaults
335 // {
336 description =
337 "The ${name'} package to use." + (if extraDescription == "" then "" else " ") + extraDescription;
338 type = with lib.types; (if nullable then nullOr else lib.id) package;
339 }
340 // optionalAttrs (example != null) {
341 example = literalExpression (
342 if isList example then "${pkgsText}.${showAttrPath example}" else example
343 );
344 }
345 );
346
347 /**
348 This option accepts arbitrary definitions, but it does not produce an option value.
349
350 This is useful for sharing a module across different module sets
351 without having to implement similar features as long as the
352 values of the options are not accessed.
353
354 # Inputs
355
356 `attrs`
357
358 : Attribute set whose attributes override the argument to `mkOption`.
359 */
360 mkSinkUndeclaredOptions =
361 attrs:
362 mkOption (
363 {
364 internal = true;
365 visible = false;
366 default = false;
367 description = "Sink for option definitions.";
368 type = mkOptionType {
369 name = "sink";
370 check = x: true;
371 merge = loc: defs: false;
372 };
373 apply = x: throw "Option value is not readable because the option is not declared.";
374 }
375 // attrs
376 );
377
378 /**
379 A merge function that merges multiple definitions of an option into a single value
380
381 :::{.caution}
382 This function is used as the default merge operation in `lib.types.mkOptionType`. In most cases, explicit usage of this function is unnecessary.
383 :::
384
385 # Inputs
386
387 `loc`
388 : location of the option in the configuration as a list of strings.
389
390 e.g. `["boot" "loader "grub" "enable"]`
391
392 `defs`
393 : list of definition values and locations.
394
395 e.g. `[ { file = "/foo.nix"; value = 1; } { file = "/bar.nix"; value = 2 } ]`
396
397 # Example
398 :::{.example}
399 ## `lib.options.mergeDefaultOption` usage example
400
401 ```nix
402 myType = mkOptionType {
403 name = "myType";
404 merge = mergeDefaultOption; # <- This line is redundant. It is the default already.
405 };
406 ```
407
408 :::
409
410 # Merge behavior
411
412 Merging requires all definition values to have the same type.
413
414 - If all definitions are booleans, the result of a `foldl'` with the `or` operation is returned.
415 - If all definitions are strings, they are concatenated. (`lib.concatStrings`)
416 - If all definitions are integers and all are equal, the first one is returned.
417 - If all definitions are lists, they are concatenated. (`++`)
418 - If all definitions are attribute sets, they are merged. (`lib.mergeAttrs`)
419 - If all definitions are functions, the first function is applied to the result of the second function. (`f -> x: f x`)
420 - Otherwise, an error is thrown.
421 */
422 mergeDefaultOption =
423 loc: defs:
424 let
425 list = getValues defs;
426 in
427 if length list == 1 then
428 head list
429 else if all isFunction list then
430 x: mergeDefaultOption loc (map (f: f x) list)
431 else if all isList list then
432 concatLists list
433 else if all isAttrs list then
434 foldl' lib.mergeAttrs { } list
435 else if all isBool list then
436 foldl' lib.or false list
437 else if all isString list then
438 lib.concatStrings list
439 else if all isInt list && all (x: x == head list) list then
440 head list
441 else
442 throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
443
444 /**
445 Require a single definition.
446
447 WARNING: Does not perform nested checks, as this does not run the merge function!
448 */
449 mergeOneOption = mergeUniqueOption { message = ""; };
450
451 /**
452 Require a single definition.
453
454 NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc).
455
456 # Inputs
457
458 `loc`
459
460 : 2\. Function argument
461
462 `defs`
463
464 : 3\. Function argument
465 */
466 mergeUniqueOption =
467 args@{
468 message,
469 # WARNING: the default merge function assumes that the definition is a valid (option) value. You MUST pass a merge function if the return value needs to be
470 # - type checked beyond what .check does (which should be very little; only on the value head; not attribute values, etc)
471 # - if you want attribute values to be checked, or list items
472 # - if you want coercedTo-like behavior to work
473 merge ? loc: defs: (head defs).value,
474 }:
475 loc: defs:
476 if length defs == 1 then
477 merge loc defs
478 else
479 assert length defs > 1;
480 throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
481
482 /**
483 "Merge" option definitions by checking that they all have the same value.
484
485 # Inputs
486
487 `loc`
488
489 : 1\. Function argument
490
491 `defs`
492
493 : 2\. Function argument
494 */
495 mergeEqualOption =
496 loc: defs:
497 if defs == [ ] then
498 abort "This case should never happen."
499 # Returns early if we only have one element
500 # This also makes it work for functions, because the foldl' below would try
501 # to compare the first element with itself, which is false for functions
502 else if length defs == 1 then
503 (head defs).value
504 else
505 (foldl' (
506 first: def:
507 if def.value != first.value then
508 throw "The option `${showOption loc}' has conflicting definition values:${
509 showDefs [
510 first
511 def
512 ]
513 }\n${prioritySuggestion}"
514 else
515 first
516 ) (head defs) (tail defs)).value;
517
518 /**
519 Extracts values of all "value" keys of the given list.
520
521 # Type
522
523 ```
524 getValues :: [ { value :: a; } ] -> [a]
525 ```
526
527 # Examples
528 :::{.example}
529 ## `getValues` usage example
530
531 ```nix
532 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
533 getValues [ ] // => [ ]
534 ```
535
536 :::
537 */
538 getValues = map (x: x.value);
539
540 /**
541 Extracts values of all "file" keys of the given list
542
543 # Type
544
545 ```
546 getFiles :: [ { file :: a; } ] -> [a]
547 ```
548
549 # Examples
550 :::{.example}
551 ## `getFiles` usage example
552
553 ```nix
554 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
555 getFiles [ ] // => [ ]
556 ```
557
558 :::
559 */
560 getFiles = map (x: x.file);
561
562 # Generate documentation template from the list of option declaration like
563 # the set generated with filterOptionSets.
564 optionAttrSetToDocList = optionAttrSetToDocList' [ ];
565
566 optionAttrSetToDocList' =
567 _: options:
568 concatMap (
569 opt:
570 let
571 name = showOption opt.loc;
572 visible = opt.visible or true;
573 docOption = {
574 loc = opt.loc;
575 inherit name;
576 description = opt.description or null;
577 declarations = filter (x: x != unknownModule) opt.declarations;
578 internal = opt.internal or false;
579 visible = if isBool visible then visible else visible == "shallow";
580 readOnly = opt.readOnly or false;
581 type = opt.type.description or "unspecified";
582 }
583 // optionalAttrs (opt ? example) {
584 example = builtins.addErrorContext "while evaluating the example of option `${name}`" (
585 renderOptionValue opt.example
586 );
587 }
588 // optionalAttrs (opt ? defaultText || opt ? default) {
589 default = builtins.addErrorContext "while evaluating the ${
590 if opt ? defaultText then "defaultText" else "default value"
591 } of option `${name}`" (renderOptionValue (opt.defaultText or opt.default));
592 }
593 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) {
594 inherit (opt) relatedPackages;
595 };
596
597 subOptions =
598 let
599 ss = opt.type.getSubOptions opt.loc;
600 in
601 if ss != { } then optionAttrSetToDocList' opt.loc ss else [ ];
602 subOptionsVisible = if isBool visible then visible else visible == "transparent";
603 in
604 # To find infinite recursion in NixOS option docs:
605 # builtins.trace opt.loc
606 [ docOption ] ++ optionals subOptionsVisible subOptions
607 ) (collect isOption options);
608
609 /**
610 This function recursively removes all derivation attributes from
611 `x` except for the `name` attribute.
612
613 This is to make the generation of `options.xml` much more
614 efficient: the XML representation of derivations is very large
615 (on the order of megabytes) and is not actually used by the
616 manual generator.
617
618 This function was made obsolete by renderOptionValue and is kept for
619 compatibility with out-of-tree code.
620
621 # Inputs
622
623 `x`
624
625 : 1\. Function argument
626 */
627 scrubOptionValue =
628 x:
629 if isDerivation x then
630 {
631 type = "derivation";
632 drvPath = x.name;
633 outPath = x.name;
634 name = x.name;
635 }
636 else if isList x then
637 map scrubOptionValue x
638 else if isAttrs x then
639 mapAttrs (n: v: scrubOptionValue v) (removeAttrs x [ "_args" ])
640 else
641 x;
642
643 /**
644 Ensures that the given option value (default or example) is a `_type`d string
645 by rendering Nix values to `literalExpression`s.
646
647 # Inputs
648
649 `v`
650
651 : 1\. Function argument
652 */
653 renderOptionValue =
654 v:
655 if v ? _type && v ? text then
656 v
657 else
658 literalExpression (
659 lib.generators.toPretty {
660 multiline = true;
661 allowPrettyValues = true;
662 } v
663 );
664
665 /**
666 For use in the `defaultText` and `example` option attributes. Causes the
667 given string to be rendered verbatim in the documentation as Nix code. This
668 is necessary for complex values, e.g. functions, or values that depend on
669 other values or packages.
670
671 # Inputs
672
673 `text`
674
675 : 1\. Function argument
676 */
677 literalExpression =
678 text:
679 if !isString text then
680 throw "literalExpression expects a string."
681 else
682 {
683 _type = "literalExpression";
684 inherit text;
685 };
686
687 literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression;
688
689 /**
690 For use in the `defaultText` and `example` option attributes. Causes the
691 given MD text to be inserted verbatim in the documentation, for when
692 a `literalExpression` would be too hard to read.
693
694 # Inputs
695
696 `text`
697
698 : 1\. Function argument
699 */
700 literalMD =
701 text:
702 if !isString text then
703 throw "literalMD expects a string."
704 else
705 {
706 _type = "literalMD";
707 inherit text;
708 };
709
710 # Helper functions.
711
712 /**
713 Convert an option, described as a list of the option parts to a
714 human-readable version.
715
716 # Inputs
717
718 `parts`
719
720 : 1\. Function argument
721
722 # Examples
723 :::{.example}
724 ## `showOption` usage example
725
726 ```nix
727 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
728 (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
729 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable"
730
731 Placeholders will not be quoted as they are not actual values:
732 (showOption ["foo" "*" "bar"]) == "foo.*.bar"
733 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
734 (showOption ["foo" "<myPlaceholder>" "bar"]) == "foo.<myPlaceholder>.bar"
735 ```
736
737 :::
738 */
739 showOption =
740 parts:
741 let
742 # If the part is a named placeholder of the form "<...>" don't escape it.
743 # It may cause misleading escaping if somebody uses literally "<...>" in their option names.
744 # This is the trade-off to allow for placeholders in option names.
745 isNamedPlaceholder = builtins.match "<(.*)>";
746 escapeOptionPart =
747 part:
748 if part == "*" || isNamedPlaceholder part != null then
749 part
750 else
751 lib.strings.escapeNixIdentifier part;
752 in
753 (concatStringsSep ".") (map escapeOptionPart parts);
754 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
755
756 showDefs =
757 defs:
758 concatMapStrings (
759 def:
760 let
761 # Pretty print the value for display, if successful
762 prettyEval = builtins.tryEval (
763 lib.generators.toPretty { } (
764 lib.generators.withRecursion {
765 depthLimit = 10;
766 throwOnDepthLimit = false;
767 } def.value
768 )
769 );
770 # Split it into its lines
771 lines = filter (v: !isList v) (builtins.split "\n" prettyEval.value);
772 # Only display the first 5 lines, and indent them for better visibility
773 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
774 result =
775 # Don't print any value if evaluating the value strictly fails
776 if !prettyEval.success then
777 ""
778 # Put it on a new line if it consists of multiple
779 else if length lines > 1 then
780 ":\n " + value
781 else
782 ": " + value;
783 in
784 "\n- In `${def.file}'${result}"
785 ) defs;
786
787 /**
788 Pretty prints all option definition locations
789
790 # Inputs
791
792 `option`
793 : The option to pretty print
794
795 # Examples
796 :::{.example}
797 ## `lib.options.showOptionWithDefLocs` usage example
798
799 ```nix
800 showOptionWithDefLocs { loc = ["x" "y" ]; files = [ "foo.nix" "bar.nix" ]; }
801 "x.y, with values defined in:\n - foo.nix\n - bar.nix\n"
802 ```
803
804 ```nix
805 nix-repl> eval = lib.evalModules {
806 modules = [
807 {
808 options = {
809 foo = lib.mkEnableOption "foo";
810 };
811 }
812 ];
813 }
814
815 nix-repl> lib.options.showOptionWithDefLocs eval.options.foo
816 "foo, with values defined in:\n - <unknown-file>\n"
817 ```
818
819 :::
820
821 # Type
822
823 ```
824 showDefsSep :: { files :: [ String ]; loc :: [ String ]; ... } -> string
825 ```
826 */
827 showOptionWithDefLocs = opt: ''
828 ${showOption opt.loc}, with values defined in:
829 ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files}
830 '';
831
832 unknownModule = "<unknown-file>";
833
834}