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