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 Deprecated alias of mkPackageOption, to be removed in 25.05.
349
350 Previously used to create options with markdown documentation, which is no longer required.
351 */
352 mkPackageOptionMD = lib.warn "mkPackageOptionMD is deprecated and will be removed in 25.05; please use mkPackageOption." mkPackageOption;
353
354 /**
355 This option accepts arbitrary definitions, but it does not produce an option value.
356
357 This is useful for sharing a module across different module sets
358 without having to implement similar features as long as the
359 values of the options are not accessed.
360
361 # Inputs
362
363 `attrs`
364
365 : Attribute set whose attributes override the argument to `mkOption`.
366 */
367 mkSinkUndeclaredOptions =
368 attrs:
369 mkOption (
370 {
371 internal = true;
372 visible = false;
373 default = false;
374 description = "Sink for option definitions.";
375 type = mkOptionType {
376 name = "sink";
377 check = x: true;
378 merge = loc: defs: false;
379 };
380 apply = x: throw "Option value is not readable because the option is not declared.";
381 }
382 // attrs
383 );
384
385 /**
386 A merge function that merges multiple definitions of an option into a single value
387
388 :::{.caution}
389 This function is used as the default merge operation in `lib.types.mkOptionType`. In most cases, explicit usage of this function is unnecessary.
390 :::
391
392 # Inputs
393
394 `loc`
395 : location of the option in the configuration as a list of strings.
396
397 e.g. `["boot" "loader "grub" "enable"]`
398
399 `defs`
400 : list of definition values and locations.
401
402 e.g. `[ { file = "/foo.nix"; value = 1; } { file = "/bar.nix"; value = 2 } ]`
403
404 # Example
405 :::{.example}
406 ## `lib.options.mergeDefaultOption` usage example
407
408 ```nix
409 myType = mkOptionType {
410 name = "myType";
411 merge = mergeDefaultOption; # <- This line is redundant. It is the default already.
412 };
413 ```
414
415 :::
416
417 # Merge behavior
418
419 Merging requires all definition values to have the same type.
420
421 - If all definitions are booleans, the result of a `foldl'` with the `or` operation is returned.
422 - If all definitions are strings, they are concatenated. (`lib.concatStrings`)
423 - If all definitions are integers and all are equal, the first one is returned.
424 - If all definitions are lists, they are concatenated. (`++`)
425 - If all definitions are attribute sets, they are merged. (`lib.mergeAttrs`)
426 - If all definitions are functions, the first function is applied to the result of the second function. (`f -> x: f x`)
427 - Otherwise, an error is thrown.
428 */
429 mergeDefaultOption =
430 loc: defs:
431 let
432 list = getValues defs;
433 in
434 if length list == 1 then
435 head list
436 else if all isFunction list then
437 x: mergeDefaultOption loc (map (f: f x) list)
438 else if all isList list then
439 concatLists list
440 else if all isAttrs list then
441 foldl' lib.mergeAttrs { } list
442 else if all isBool list then
443 foldl' lib.or false list
444 else if all isString list then
445 lib.concatStrings list
446 else if all isInt list && all (x: x == head list) list then
447 head list
448 else
449 throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
450
451 /**
452 Require a single definition.
453
454 WARNING: Does not perform nested checks, as this does not run the merge function!
455 */
456 mergeOneOption = mergeUniqueOption { message = ""; };
457
458 /**
459 Require a single definition.
460
461 NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc).
462
463 # Inputs
464
465 `loc`
466
467 : 2\. Function argument
468
469 `defs`
470
471 : 3\. Function argument
472 */
473 mergeUniqueOption =
474 args@{
475 message,
476 # 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
477 # - type checked beyond what .check does (which should be very little; only on the value head; not attribute values, etc)
478 # - if you want attribute values to be checked, or list items
479 # - if you want coercedTo-like behavior to work
480 merge ? loc: defs: (head defs).value,
481 }:
482 loc: defs:
483 if length defs == 1 then
484 merge loc defs
485 else
486 assert length defs > 1;
487 throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
488
489 /**
490 "Merge" option definitions by checking that they all have the same value.
491
492 # Inputs
493
494 `loc`
495
496 : 1\. Function argument
497
498 `defs`
499
500 : 2\. Function argument
501 */
502 mergeEqualOption =
503 loc: defs:
504 if defs == [ ] then
505 abort "This case should never happen."
506 # Return early if we only have one element
507 # This also makes it work for functions, because the foldl' below would try
508 # to compare the first element with itself, which is false for functions
509 else if length defs == 1 then
510 (head defs).value
511 else
512 (foldl' (
513 first: def:
514 if def.value != first.value then
515 throw "The option `${showOption loc}' has conflicting definition values:${
516 showDefs [
517 first
518 def
519 ]
520 }\n${prioritySuggestion}"
521 else
522 first
523 ) (head defs) (tail defs)).value;
524
525 /**
526 Extracts values of all "value" keys of the given list.
527
528 # Type
529
530 ```
531 getValues :: [ { value :: a; } ] -> [a]
532 ```
533
534 # Examples
535 :::{.example}
536 ## `getValues` usage example
537
538 ```nix
539 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
540 getValues [ ] // => [ ]
541 ```
542
543 :::
544 */
545 getValues = map (x: x.value);
546
547 /**
548 Extracts values of all "file" keys of the given list
549
550 # Type
551
552 ```
553 getFiles :: [ { file :: a; } ] -> [a]
554 ```
555
556 # Examples
557 :::{.example}
558 ## `getFiles` usage example
559
560 ```nix
561 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
562 getFiles [ ] // => [ ]
563 ```
564
565 :::
566 */
567 getFiles = map (x: x.file);
568
569 # Generate documentation template from the list of option declaration like
570 # the set generated with filterOptionSets.
571 optionAttrSetToDocList = optionAttrSetToDocList' [ ];
572
573 optionAttrSetToDocList' =
574 _: options:
575 concatMap (
576 opt:
577 let
578 name = showOption opt.loc;
579 visible = opt.visible or true;
580 docOption = {
581 loc = opt.loc;
582 inherit name;
583 description = opt.description or null;
584 declarations = filter (x: x != unknownModule) opt.declarations;
585 internal = opt.internal or false;
586 visible = if isBool visible then visible else visible == "shallow";
587 readOnly = opt.readOnly or false;
588 type = opt.type.description or "unspecified";
589 }
590 // optionalAttrs (opt ? example) {
591 example = builtins.addErrorContext "while evaluating the example of option `${name}`" (
592 renderOptionValue opt.example
593 );
594 }
595 // optionalAttrs (opt ? defaultText || opt ? default) {
596 default = builtins.addErrorContext "while evaluating the ${
597 if opt ? defaultText then "defaultText" else "default value"
598 } of option `${name}`" (renderOptionValue (opt.defaultText or opt.default));
599 }
600 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) {
601 inherit (opt) relatedPackages;
602 };
603
604 subOptions =
605 let
606 ss = opt.type.getSubOptions opt.loc;
607 in
608 if ss != { } then optionAttrSetToDocList' opt.loc ss else [ ];
609 subOptionsVisible = if isBool visible then visible else visible == "transparent";
610 in
611 # To find infinite recursion in NixOS option docs:
612 # builtins.trace opt.loc
613 [ docOption ] ++ optionals subOptionsVisible subOptions
614 ) (collect isOption options);
615
616 /**
617 This function recursively removes all derivation attributes from
618 `x` except for the `name` attribute.
619
620 This is to make the generation of `options.xml` much more
621 efficient: the XML representation of derivations is very large
622 (on the order of megabytes) and is not actually used by the
623 manual generator.
624
625 This function was made obsolete by renderOptionValue and is kept for
626 compatibility with out-of-tree code.
627
628 # Inputs
629
630 `x`
631
632 : 1\. Function argument
633 */
634 scrubOptionValue =
635 x:
636 if isDerivation x then
637 {
638 type = "derivation";
639 drvPath = x.name;
640 outPath = x.name;
641 name = x.name;
642 }
643 else if isList x then
644 map scrubOptionValue x
645 else if isAttrs x then
646 mapAttrs (n: v: scrubOptionValue v) (removeAttrs x [ "_args" ])
647 else
648 x;
649
650 /**
651 Ensures that the given option value (default or example) is a `_type`d string
652 by rendering Nix values to `literalExpression`s.
653
654 # Inputs
655
656 `v`
657
658 : 1\. Function argument
659 */
660 renderOptionValue =
661 v:
662 if v ? _type && v ? text then
663 v
664 else
665 literalExpression (
666 lib.generators.toPretty {
667 multiline = true;
668 allowPrettyValues = true;
669 } v
670 );
671
672 /**
673 For use in the `defaultText` and `example` option attributes. Causes the
674 given string to be rendered verbatim in the documentation as Nix code. This
675 is necessary for complex values, e.g. functions, or values that depend on
676 other values or packages.
677
678 # Inputs
679
680 `text`
681
682 : 1\. Function argument
683 */
684 literalExpression =
685 text:
686 if !isString text then
687 throw "literalExpression expects a string."
688 else
689 {
690 _type = "literalExpression";
691 inherit text;
692 };
693
694 literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression;
695
696 /**
697 For use in the `defaultText` and `example` option attributes. Causes the
698 given MD text to be inserted verbatim in the documentation, for when
699 a `literalExpression` would be too hard to read.
700
701 # Inputs
702
703 `text`
704
705 : 1\. Function argument
706 */
707 literalMD =
708 text:
709 if !isString text then
710 throw "literalMD expects a string."
711 else
712 {
713 _type = "literalMD";
714 inherit text;
715 };
716
717 # Helper functions.
718
719 /**
720 Convert an option, described as a list of the option parts to a
721 human-readable version.
722
723 # Inputs
724
725 `parts`
726
727 : 1\. Function argument
728
729 # Examples
730 :::{.example}
731 ## `showOption` usage example
732
733 ```nix
734 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
735 (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
736 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable"
737
738 Placeholders will not be quoted as they are not actual values:
739 (showOption ["foo" "*" "bar"]) == "foo.*.bar"
740 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
741 (showOption ["foo" "<myPlaceholder>" "bar"]) == "foo.<myPlaceholder>.bar"
742 ```
743
744 :::
745 */
746 showOption =
747 parts:
748 let
749 # If the part is a named placeholder of the form "<...>" don't escape it.
750 # It may cause misleading escaping if somebody uses literally "<...>" in their option names.
751 # This is the trade-off to allow for placeholders in option names.
752 isNamedPlaceholder = builtins.match "<(.*)>";
753 escapeOptionPart =
754 part:
755 if part == "*" || isNamedPlaceholder part != null then
756 part
757 else
758 lib.strings.escapeNixIdentifier part;
759 in
760 (concatStringsSep ".") (map escapeOptionPart parts);
761 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
762
763 showDefs =
764 defs:
765 concatMapStrings (
766 def:
767 let
768 # Pretty print the value for display, if successful
769 prettyEval = builtins.tryEval (
770 lib.generators.toPretty { } (
771 lib.generators.withRecursion {
772 depthLimit = 10;
773 throwOnDepthLimit = false;
774 } def.value
775 )
776 );
777 # Split it into its lines
778 lines = filter (v: !isList v) (builtins.split "\n" prettyEval.value);
779 # Only display the first 5 lines, and indent them for better visibility
780 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
781 result =
782 # Don't print any value if evaluating the value strictly fails
783 if !prettyEval.success then
784 ""
785 # Put it on a new line if it consists of multiple
786 else if length lines > 1 then
787 ":\n " + value
788 else
789 ": " + value;
790 in
791 "\n- In `${def.file}'${result}"
792 ) defs;
793
794 /**
795 Pretty prints all option definition locations
796
797 # Inputs
798
799 `option`
800 : The option to pretty print
801
802 # Examples
803 :::{.example}
804 ## `lib.options.showOptionWithDefLocs` usage example
805
806 ```nix
807 showOptionWithDefLocs { loc = ["x" "y" ]; files = [ "foo.nix" "bar.nix" ]; }
808 "x.y, with values defined in:\n - foo.nix\n - bar.nix\n"
809 ```
810
811 ```nix
812 nix-repl> eval = lib.evalModules {
813 modules = [
814 {
815 options = {
816 foo = lib.mkEnableOption "foo";
817 };
818 }
819 ];
820 }
821
822 nix-repl> lib.options.showOptionWithDefLocs eval.options.foo
823 "foo, with values defined in:\n - <unknown-file>\n"
824 ```
825
826 :::
827
828 # Type
829
830 ```
831 showDefsSep :: { files :: [ String ]; loc :: [ String ]; ... } -> string
832 ```
833 */
834 showOptionWithDefLocs = opt: ''
835 ${showOption opt.loc}, with values defined in:
836 ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files}
837 '';
838
839 unknownModule = "<unknown-file>";
840
841}