opencomposite: add meta.platforms (#357198)
[NixPkgs.git] / lib / options.nix
blobf4d0d9d36cfc9308396a1de121aebaee0be7106e
1 /* Nixpkgs/NixOS option handling. */
2 { lib }:
4 let
5   inherit (lib)
6     all
7     collect
8     concatLists
9     concatMap
10     concatMapStringsSep
11     filter
12     foldl'
13     head
14     tail
15     isAttrs
16     isBool
17     isDerivation
18     isFunction
19     isInt
20     isList
21     isString
22     length
23     mapAttrs
24     optional
25     optionals
26     take
27     ;
28   inherit (lib.attrsets)
29     attrByPath
30     optionalAttrs
31     ;
32   inherit (lib.strings)
33     concatMapStrings
34     concatStringsSep
35     ;
36   inherit (lib.types)
37     mkOptionType
38     ;
39   inherit (lib.lists)
40     last
41     ;
42   prioritySuggestion = ''
43    Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions.
44   '';
46 rec {
48   /* Returns true when the given argument is an option
50      Type: isOption :: a -> bool
52      Example:
53        isOption 1             // => false
54        isOption (mkOption {}) // => true
55   */
56   isOption = lib.isType "option";
58   /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
60      All keys default to `null` when not given.
62      Example:
63        mkOption { }  // => { _type = "option"; }
64        mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; }
65   */
66   mkOption =
67     {
68     # Default value used when no definition is given in the configuration.
69     default ? null,
70     # Textual representation of the default, for the manual.
71     defaultText ? null,
72     # Example value used in the manual.
73     example ? null,
74     # String describing the option.
75     description ? null,
76     # Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix).
77     relatedPackages ? null,
78     # Option type, providing type-checking and value merging.
79     type ? null,
80     # Function that converts the option value to something else.
81     apply ? null,
82     # Whether the option is for NixOS developers only.
83     internal ? null,
84     # 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.
85     visible ? null,
86     # Whether the option can be set only once
87     readOnly ? null,
88     } @ attrs:
89     attrs // { _type = "option"; };
91   /* Creates an Option attribute set for a boolean value option i.e an
92      option to be toggled on or off:
94      Example:
95        mkEnableOption "foo"
96        => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; }
97   */
98   mkEnableOption =
99     # Name for the created option
100     name: mkOption {
101     default = false;
102     example = true;
103     description = "Whether to enable ${name}.";
104     type = lib.types.bool;
105   };
107   /* Creates an Option attribute set for an option that specifies the
108      package a module should use for some purpose.
110      The package is specified in the third argument under `default` as a list of strings
111      representing its attribute path in nixpkgs (or another package set).
112      Because of this, you need to pass nixpkgs itself (usually `pkgs` in a module;
113      alternatively to nixpkgs itself, another package set) as the first argument.
115      If you pass another package set you should set the `pkgsText` option.
116      This option is used to display the expression for the package set. It is `"pkgs"` by default.
117      If your expression is complex you should parenthesize it, as the `pkgsText` argument
118      is usually immediately followed by an attribute lookup (`.`).
120      The second argument may be either a string or a list of strings.
121      It provides the display name of the package in the description of the generated option
122      (using only the last element if the passed value is a list)
123      and serves as the fallback value for the `default` argument.
125      To include extra information in the description, pass `extraDescription` to
126      append arbitrary text to the generated description.
128      You can also pass an `example` value, either a literal string or an attribute path.
130      The `default` argument can be omitted if the provided name is
131      an attribute of pkgs (if `name` is a string) or a valid attribute path in pkgs (if `name` is a list).
132      You can also set `default` to just a string in which case it is interpreted as an attribute name
133      (a singleton attribute path, if you will).
135      If you wish to explicitly provide no default, pass `null` as `default`.
137      If you want users to be able to set no package, pass `nullable = true`.
138      In this mode a `default = null` will not be interpreted as no default and is interpreted literally.
140      Type: mkPackageOption :: pkgs -> (string|[string]) -> { nullable? :: bool, default? :: string|[string], example? :: null|string|[string], extraDescription? :: string, pkgsText? :: string } -> option
142      Example:
143        mkPackageOption pkgs "hello" { }
144        => { ...; default = pkgs.hello; defaultText = literalExpression "pkgs.hello"; description = "The hello package to use."; type = package; }
146      Example:
147        mkPackageOption pkgs "GHC" {
148          default = [ "ghc" ];
149          example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])";
150        }
151        => { ...; 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; }
153      Example:
154        mkPackageOption pkgs [ "python3Packages" "pytorch" ] {
155          extraDescription = "This is an example and doesn't actually do anything.";
156        }
157        => { ...; 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; }
159      Example:
160        mkPackageOption pkgs "nushell" {
161          nullable = true;
162        }
163        => { ...; default = pkgs.nushell; defaultText = literalExpression "pkgs.nushell"; description = "The nushell package to use."; type = nullOr package; }
165      Example:
166        mkPackageOption pkgs "coreutils" {
167          default = null;
168        }
169        => { ...; description = "The coreutils package to use."; type = package; }
171      Example:
172        mkPackageOption pkgs "dbus" {
173          nullable = true;
174          default = null;
175        }
176        => { ...; default = null; description = "The dbus package to use."; type = nullOr package; }
178      Example:
179        mkPackageOption pkgs.javaPackages "OpenJFX" {
180          default = "openjfx20";
181          pkgsText = "pkgs.javaPackages";
182        }
183        => { ...; default = pkgs.javaPackages.openjfx20; defaultText = literalExpression "pkgs.javaPackages.openjfx20"; description = "The OpenJFX package to use."; type = package; }
184   */
185   mkPackageOption =
186     # Package set (an instantiation of nixpkgs such as pkgs in modules or another package set)
187     pkgs:
188       # Name for the package, shown in option description
189       name:
190       {
191         # Whether the package can be null, for example to disable installing a package altogether (defaults to false)
192         nullable ? false,
193         # The attribute path where the default package is located (may be omitted, in which case it is copied from `name`)
194         default ? name,
195         # A string or an attribute path to use as an example (may be omitted)
196         example ? null,
197         # Additional text to include in the option description (may be omitted)
198         extraDescription ? "",
199         # Representation of the package set passed as pkgs (defaults to `"pkgs"`)
200         pkgsText ? "pkgs"
201       }:
202       let
203         name' = if isList name then last name else name;
204         default' = if isList default then default else [ default ];
205         defaultText = concatStringsSep "." default';
206         defaultValue = attrByPath default'
207           (throw "${defaultText} cannot be found in ${pkgsText}") pkgs;
208         defaults = if default != null then {
209           default = defaultValue;
210           defaultText = literalExpression ("${pkgsText}." + defaultText);
211         } else optionalAttrs nullable {
212           default = null;
213         };
214       in mkOption (defaults // {
215         description = "The ${name'} package to use."
216           + (if extraDescription == "" then "" else " ") + extraDescription;
217         type = with lib.types; (if nullable then nullOr else lib.id) package;
218       } // optionalAttrs (example != null) {
219         example = literalExpression
220           (if isList example then "${pkgsText}." + concatStringsSep "." example else example);
221       });
223   /* Deprecated alias of mkPackageOption, to be removed in 25.05.
224      Previously used to create options with markdown documentation, which is no longer required.
225   */
226   mkPackageOptionMD = lib.warn "mkPackageOptionMD is deprecated and will be removed in 25.05; please use mkPackageOption." mkPackageOption;
228   /* This option accepts anything, but it does not produce any result.
230      This is useful for sharing a module across different module sets
231      without having to implement similar features as long as the
232      values of the options are not accessed. */
233   mkSinkUndeclaredOptions = attrs: mkOption ({
234     internal = true;
235     visible = false;
236     default = false;
237     description = "Sink for option definitions.";
238     type = mkOptionType {
239       name = "sink";
240       check = x: true;
241       merge = loc: defs: false;
242     };
243     apply = x: throw "Option value is not readable because the option is not declared.";
244   } // attrs);
246   mergeDefaultOption = loc: defs:
247     let list = getValues defs; in
248     if length list == 1 then head list
249     else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list)
250     else if all isList list then concatLists list
251     else if all isAttrs list then foldl' lib.mergeAttrs {} list
252     else if all isBool list then foldl' lib.or false list
253     else if all isString list then lib.concatStrings list
254     else if all isInt list && all (x: x == head list) list then head list
255     else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
257   /*
258     Require a single definition.
260     WARNING: Does not perform nested checks, as this does not run the merge function!
261     */
262   mergeOneOption = mergeUniqueOption { message = ""; };
264   /*
265     Require a single definition.
267     NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc).
268    */
269   mergeUniqueOption = args@{
270       message,
271       # 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
272       #   - type checked beyond what .check does (which should be very litte; only on the value head; not attribute values, etc)
273       #   - if you want attribute values to be checked, or list items
274       #   - if you want coercedTo-like behavior to work
275       merge ? loc: defs: (head defs).value }:
276     loc: defs:
277       if length defs == 1
278       then merge loc defs
279       else
280         assert length defs > 1;
281         throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
283   /* "Merge" option definitions by checking that they all have the same value. */
284   mergeEqualOption = loc: defs:
285     if defs == [] then abort "This case should never happen."
286     # Return early if we only have one element
287     # This also makes it work for functions, because the foldl' below would try
288     # to compare the first element with itself, which is false for functions
289     else if length defs == 1 then (head defs).value
290     else (foldl' (first: def:
291       if def.value != first.value then
292         throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}\n${prioritySuggestion}"
293       else
294         first) (head defs) (tail defs)).value;
296   /* Extracts values of all "value" keys of the given list.
298      Type: getValues :: [ { value :: a; } ] -> [a]
300      Example:
301        getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
302        getValues [ ]                               // => [ ]
303   */
304   getValues = map (x: x.value);
306   /* Extracts values of all "file" keys of the given list
308      Type: getFiles :: [ { file :: a; } ] -> [a]
310      Example:
311        getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
312        getFiles [ ]                                         // => [ ]
313   */
314   getFiles = map (x: x.file);
316   # Generate documentation template from the list of option declaration like
317   # the set generated with filterOptionSets.
318   optionAttrSetToDocList = optionAttrSetToDocList' [];
320   optionAttrSetToDocList' = _: options:
321     concatMap (opt:
322       let
323         name = showOption opt.loc;
324         docOption = {
325           loc = opt.loc;
326           inherit name;
327           description = opt.description or null;
328           declarations = filter (x: x != unknownModule) opt.declarations;
329           internal = opt.internal or false;
330           visible =
331             if (opt?visible && opt.visible == "shallow")
332             then true
333             else opt.visible or true;
334           readOnly = opt.readOnly or false;
335           type = opt.type.description or "unspecified";
336         }
337         // optionalAttrs (opt ? example) {
338           example =
339             builtins.addErrorContext "while evaluating the example of option `${name}`" (
340               renderOptionValue opt.example
341             );
342         }
343         // optionalAttrs (opt ? defaultText || opt ? default) {
344           default =
345             builtins.addErrorContext "while evaluating the ${if opt?defaultText then "defaultText" else "default value"} of option `${name}`" (
346               renderOptionValue (opt.defaultText or opt.default)
347             );
348         }
349         // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; };
351         subOptions =
352           let ss = opt.type.getSubOptions opt.loc;
353           in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
354         subOptionsVisible = docOption.visible && opt.visible or null != "shallow";
355       in
356         # To find infinite recursion in NixOS option docs:
357         # builtins.trace opt.loc
358         [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options);
361   /* This function recursively removes all derivation attributes from
362      `x` except for the `name` attribute.
364      This is to make the generation of `options.xml` much more
365      efficient: the XML representation of derivations is very large
366      (on the order of megabytes) and is not actually used by the
367      manual generator.
369      This function was made obsolete by renderOptionValue and is kept for
370      compatibility with out-of-tree code.
371   */
372   scrubOptionValue = x:
373     if isDerivation x then
374       { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
375     else if isList x then map scrubOptionValue x
376     else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
377     else x;
380   /* Ensures that the given option value (default or example) is a `_type`d string
381      by rendering Nix values to `literalExpression`s.
382   */
383   renderOptionValue = v:
384     if v ? _type && v ? text then v
385     else literalExpression (lib.generators.toPretty {
386       multiline = true;
387       allowPrettyValues = true;
388     } v);
391   /* For use in the `defaultText` and `example` option attributes. Causes the
392      given string to be rendered verbatim in the documentation as Nix code. This
393      is necessary for complex values, e.g. functions, or values that depend on
394      other values or packages.
395   */
396   literalExpression = text:
397     if ! isString text then throw "literalExpression expects a string."
398     else { _type = "literalExpression"; inherit text; };
400   literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression;
402   /* Transition marker for documentation that's already migrated to markdown
403      syntax. Has been a no-op for some while and been removed from nixpkgs.
404      Kept here to alert downstream users who may not be aware of the migration's
405      completion that it should be removed from modules.
406   */
407   mdDoc = lib.warn "lib.mdDoc will be removed from nixpkgs in 24.11. Option descriptions are now in Markdown by default; you can remove any remaining uses of lib.mdDoc.";
409   /* For use in the `defaultText` and `example` option attributes. Causes the
410      given MD text to be inserted verbatim in the documentation, for when
411      a `literalExpression` would be too hard to read.
412   */
413   literalMD = text:
414     if ! isString text then throw "literalMD expects a string."
415     else { _type = "literalMD"; inherit text; };
417   # Helper functions.
419   /* Convert an option, described as a list of the option parts to a
420      human-readable version.
422      Example:
423        (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
424        (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
425        (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable"
427      Placeholders will not be quoted as they are not actual values:
428        (showOption ["foo" "*" "bar"]) == "foo.*.bar"
429        (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
430   */
431   showOption = parts: let
432     escapeOptionPart = part:
433       let
434         # We assume that these are "special values" and not real configuration data.
435         # If it is real configuration data, it is rendered incorrectly.
436         specialIdentifiers = [
437           "<name>"          # attrsOf (submodule {})
438           "*"               # listOf (submodule {})
439           "<function body>" # functionTo
440         ];
441       in if builtins.elem part specialIdentifiers
442          then part
443          else lib.strings.escapeNixIdentifier part;
444     in (concatStringsSep ".") (map escapeOptionPart parts);
445   showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
447   showDefs = defs: concatMapStrings (def:
448     let
449       # Pretty print the value for display, if successful
450       prettyEval = builtins.tryEval
451         (lib.generators.toPretty { }
452           (lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value));
453       # Split it into its lines
454       lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value);
455       # Only display the first 5 lines, and indent them for better visibility
456       value = concatStringsSep "\n    " (take 5 lines ++ optional (length lines > 5) "...");
457       result =
458         # Don't print any value if evaluating the value strictly fails
459         if ! prettyEval.success then ""
460         # Put it on a new line if it consists of multiple
461         else if length lines > 1 then ":\n    " + value
462         else ": " + value;
463     in "\n- In `${def.file}'${result}"
464   ) defs;
466   showOptionWithDefLocs = opt: ''
467       ${showOption opt.loc}, with values defined in:
468       ${concatMapStringsSep "\n" (defFile: "  - ${defFile}") opt.files}
469     '';
471   unknownModule = "<unknown-file>";