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 already.
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 little; 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 loc = opt.loc;
577 inherit name;
578 description = opt.description or null;
579 declarations = filter (x: x != unknownModule) opt.declarations;
580 internal = opt.internal or false;
581 visible = if (opt ? visible && opt.visible == "shallow") then true else opt.visible or true;
582 readOnly = opt.readOnly or false;
583 type = opt.type.description or "unspecified";
584 }
585 // optionalAttrs (opt ? example) {
586 example = builtins.addErrorContext "while evaluating the example of option `${name}`" (
587 renderOptionValue opt.example
588 );
589 }
590 // optionalAttrs (opt ? defaultText || opt ? default) {
591 default = builtins.addErrorContext "while evaluating the ${
592 if opt ? defaultText then "defaultText" else "default value"
593 } of option `${name}`" (renderOptionValue (opt.defaultText or opt.default));
594 }
595 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) {
596 inherit (opt) relatedPackages;
597 };
598
599 subOptions =
600 let
601 ss = opt.type.getSubOptions opt.loc;
602 in
603 if ss != { } then optionAttrSetToDocList' opt.loc ss else [ ];
604 subOptionsVisible = docOption.visible && opt.visible or null != "shallow";
605 in
606 # To find infinite recursion in NixOS option docs:
607 # builtins.trace opt.loc
608 [ docOption ] ++ optionals subOptionsVisible subOptions
609 ) (collect isOption options);
610
611 /**
612 This function recursively removes all derivation attributes from
613 `x` except for the `name` attribute.
614
615 This is to make the generation of `options.xml` much more
616 efficient: the XML representation of derivations is very large
617 (on the order of megabytes) and is not actually used by the
618 manual generator.
619
620 This function was made obsolete by renderOptionValue and is kept for
621 compatibility with out-of-tree code.
622
623 # Inputs
624
625 `x`
626
627 : 1\. Function argument
628 */
629 scrubOptionValue =
630 x:
631 if isDerivation x then
632 {
633 type = "derivation";
634 drvPath = x.name;
635 outPath = x.name;
636 name = x.name;
637 }
638 else if isList x then
639 map scrubOptionValue x
640 else if isAttrs x then
641 mapAttrs (n: v: scrubOptionValue v) (removeAttrs x [ "_args" ])
642 else
643 x;
644
645 /**
646 Ensures that the given option value (default or example) is a `_type`d string
647 by rendering Nix values to `literalExpression`s.
648
649 # Inputs
650
651 `v`
652
653 : 1\. Function argument
654 */
655 renderOptionValue =
656 v:
657 if v ? _type && v ? text then
658 v
659 else
660 literalExpression (
661 lib.generators.toPretty {
662 multiline = true;
663 allowPrettyValues = true;
664 } v
665 );
666
667 /**
668 For use in the `defaultText` and `example` option attributes. Causes the
669 given string to be rendered verbatim in the documentation as Nix code. This
670 is necessary for complex values, e.g. functions, or values that depend on
671 other values or packages.
672
673 # Inputs
674
675 `text`
676
677 : 1\. Function argument
678 */
679 literalExpression =
680 text:
681 if !isString text then
682 throw "literalExpression expects a string."
683 else
684 {
685 _type = "literalExpression";
686 inherit text;
687 };
688
689 literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression;
690
691 /**
692 For use in the `defaultText` and `example` option attributes. Causes the
693 given MD text to be inserted verbatim in the documentation, for when
694 a `literalExpression` would be too hard to read.
695
696 # Inputs
697
698 `text`
699
700 : 1\. Function argument
701 */
702 literalMD =
703 text:
704 if !isString text then
705 throw "literalMD expects a string."
706 else
707 {
708 _type = "literalMD";
709 inherit text;
710 };
711
712 # Helper functions.
713
714 /**
715 Convert an option, described as a list of the option parts to a
716 human-readable version.
717
718 # Inputs
719
720 `parts`
721
722 : 1\. Function argument
723
724 # Examples
725 :::{.example}
726 ## `showOption` usage example
727
728 ```nix
729 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
730 (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
731 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable"
732
733 Placeholders will not be quoted as they are not actual values:
734 (showOption ["foo" "*" "bar"]) == "foo.*.bar"
735 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
736 (showOption ["foo" "<myPlaceholder>" "bar"]) == "foo.<myPlaceholder>.bar"
737 ```
738
739 :::
740 */
741 showOption =
742 parts:
743 let
744 # If the part is a named placeholder of the form "<...>" don't escape it.
745 # It may cause misleading escaping if somebody uses literally "<...>" in their option names.
746 # This is the trade-off to allow for placeholders in option names.
747 isNamedPlaceholder = builtins.match "<(.*)>";
748 escapeOptionPart =
749 part:
750 if part == "*" || isNamedPlaceholder part != null then
751 part
752 else
753 lib.strings.escapeNixIdentifier part;
754 in
755 (concatStringsSep ".") (map escapeOptionPart parts);
756 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
757
758 showDefs =
759 defs:
760 concatMapStrings (
761 def:
762 let
763 # Pretty print the value for display, if successful
764 prettyEval = builtins.tryEval (
765 lib.generators.toPretty { } (
766 lib.generators.withRecursion {
767 depthLimit = 10;
768 throwOnDepthLimit = false;
769 } def.value
770 )
771 );
772 # Split it into its lines
773 lines = filter (v: !isList v) (builtins.split "\n" prettyEval.value);
774 # Only display the first 5 lines, and indent them for better visibility
775 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
776 result =
777 # Don't print any value if evaluating the value strictly fails
778 if !prettyEval.success then
779 ""
780 # Put it on a new line if it consists of multiple
781 else if length lines > 1 then
782 ":\n " + value
783 else
784 ": " + value;
785 in
786 "\n- In `${def.file}'${result}"
787 ) defs;
788
789 /**
790 Pretty prints all option definition locations
791
792 # Inputs
793
794 `option`
795 : The option to pretty print
796
797 # Examples
798 :::{.example}
799 ## `lib.options.showOptionWithDefLocs` usage example
800
801 ```nix
802 showOptionWithDefLocs { loc = ["x" "y" ]; files = [ "foo.nix" "bar.nix" ]; }
803 "x.y, with values defined in:\n - foo.nix\n - bar.nix\n"
804 ```
805
806 ```nix
807 nix-repl> eval = lib.evalModules {
808 modules = [
809 {
810 options = {
811 foo = lib.mkEnableOption "foo";
812 };
813 }
814 ];
815 }
816
817 nix-repl> lib.options.showOptionWithDefLocs eval.options.foo
818 "foo, with values defined in:\n - <unknown-file>\n"
819 ```
820
821 :::
822
823 # Type
824
825 ```
826 showDefsSep :: { files :: [ String ]; loc :: [ String ]; ... } -> string
827 ```
828 */
829 showOptionWithDefLocs = opt: ''
830 ${showOption opt.loc}, with values defined in:
831 ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files}
832 '';
833
834 unknownModule = "<unknown-file>";
835
836}