1/* Nixpkgs/NixOS option handling. */
2{ lib }:
3
4let
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 '';
45in
46rec {
47
48 /* Returns true when the given argument is an option
49
50 Type: isOption :: a -> bool
51
52 Example:
53 isOption 1 // => false
54 isOption (mkOption {}) // => true
55 */
56 isOption = lib.isType "option";
57
58 /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
59
60 All keys default to `null` when not given.
61
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"; };
90
91 /* Creates an Option attribute set for a boolean value option i.e an
92 option to be toggled on or off:
93
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 };
106
107 /* Creates an Option attribute set for an option that specifies the
108 package a module should use for some purpose.
109
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.
114
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 (`.`).
119
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.
124
125 To include extra information in the description, pass `extraDescription` to
126 append arbitrary text to the generated description.
127
128 You can also pass an `example` value, either a literal string or an attribute path.
129
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).
134
135 If you wish to explicitly provide no default, pass `null` as `default`.
136
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.
139
140 Type: mkPackageOption :: pkgs -> (string|[string]) -> { nullable? :: bool, default? :: string|[string], example? :: null|string|[string], extraDescription? :: string, pkgsText? :: string } -> option
141
142 Example:
143 mkPackageOption pkgs "hello" { }
144 => { ...; default = pkgs.hello; defaultText = literalExpression "pkgs.hello"; description = "The hello package to use."; type = package; }
145
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; }
152
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; }
158
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; }
164
165 Example:
166 mkPackageOption pkgs "coreutils" {
167 default = null;
168 }
169 => { ...; description = "The coreutils package to use."; type = package; }
170
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; }
177
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 });
222
223 /* Alias of mkPackageOption. Previously used to create options with markdown
224 documentation, which is no longer required.
225 */
226 mkPackageOptionMD = mkPackageOption;
227
228 /* This option accepts anything, but it does not produce any result.
229
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);
245
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}";
256
257 mergeOneOption = mergeUniqueOption { message = ""; };
258
259 mergeUniqueOption = { message }: loc: defs:
260 if length defs == 1
261 then (head defs).value
262 else assert length defs > 1;
263 throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
264
265 /* "Merge" option definitions by checking that they all have the same value. */
266 mergeEqualOption = loc: defs:
267 if defs == [] then abort "This case should never happen."
268 # Return early if we only have one element
269 # This also makes it work for functions, because the foldl' below would try
270 # to compare the first element with itself, which is false for functions
271 else if length defs == 1 then (head defs).value
272 else (foldl' (first: def:
273 if def.value != first.value then
274 throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}\n${prioritySuggestion}"
275 else
276 first) (head defs) (tail defs)).value;
277
278 /* Extracts values of all "value" keys of the given list.
279
280 Type: getValues :: [ { value :: a; } ] -> [a]
281
282 Example:
283 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
284 getValues [ ] // => [ ]
285 */
286 getValues = map (x: x.value);
287
288 /* Extracts values of all "file" keys of the given list
289
290 Type: getFiles :: [ { file :: a; } ] -> [a]
291
292 Example:
293 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
294 getFiles [ ] // => [ ]
295 */
296 getFiles = map (x: x.file);
297
298 # Generate documentation template from the list of option declaration like
299 # the set generated with filterOptionSets.
300 optionAttrSetToDocList = optionAttrSetToDocList' [];
301
302 optionAttrSetToDocList' = _: options:
303 concatMap (opt:
304 let
305 name = showOption opt.loc;
306 docOption = {
307 loc = opt.loc;
308 inherit name;
309 description = opt.description or null;
310 declarations = filter (x: x != unknownModule) opt.declarations;
311 internal = opt.internal or false;
312 visible =
313 if (opt?visible && opt.visible == "shallow")
314 then true
315 else opt.visible or true;
316 readOnly = opt.readOnly or false;
317 type = opt.type.description or "unspecified";
318 }
319 // optionalAttrs (opt ? example) {
320 example =
321 builtins.addErrorContext "while evaluating the example of option `${name}`" (
322 renderOptionValue opt.example
323 );
324 }
325 // optionalAttrs (opt ? defaultText || opt ? default) {
326 default =
327 builtins.addErrorContext "while evaluating the ${if opt?defaultText then "defaultText" else "default value"} of option `${name}`" (
328 renderOptionValue (opt.defaultText or opt.default)
329 );
330 }
331 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; };
332
333 subOptions =
334 let ss = opt.type.getSubOptions opt.loc;
335 in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
336 subOptionsVisible = docOption.visible && opt.visible or null != "shallow";
337 in
338 # To find infinite recursion in NixOS option docs:
339 # builtins.trace opt.loc
340 [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options);
341
342
343 /* This function recursively removes all derivation attributes from
344 `x` except for the `name` attribute.
345
346 This is to make the generation of `options.xml` much more
347 efficient: the XML representation of derivations is very large
348 (on the order of megabytes) and is not actually used by the
349 manual generator.
350
351 This function was made obsolete by renderOptionValue and is kept for
352 compatibility with out-of-tree code.
353 */
354 scrubOptionValue = x:
355 if isDerivation x then
356 { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
357 else if isList x then map scrubOptionValue x
358 else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
359 else x;
360
361
362 /* Ensures that the given option value (default or example) is a `_type`d string
363 by rendering Nix values to `literalExpression`s.
364 */
365 renderOptionValue = v:
366 if v ? _type && v ? text then v
367 else literalExpression (lib.generators.toPretty {
368 multiline = true;
369 allowPrettyValues = true;
370 } v);
371
372
373 /* For use in the `defaultText` and `example` option attributes. Causes the
374 given string to be rendered verbatim in the documentation as Nix code. This
375 is necessary for complex values, e.g. functions, or values that depend on
376 other values or packages.
377 */
378 literalExpression = text:
379 if ! isString text then throw "literalExpression expects a string."
380 else { _type = "literalExpression"; inherit text; };
381
382 literalExample = lib.warn "literalExample is deprecated, use literalExpression instead, or use literalMD for a non-Nix description." literalExpression;
383
384 /* Transition marker for documentation that's already migrated to markdown
385 syntax. This is a no-op and no longer needed.
386 */
387 mdDoc = lib.id;
388
389 /* For use in the `defaultText` and `example` option attributes. Causes the
390 given MD text to be inserted verbatim in the documentation, for when
391 a `literalExpression` would be too hard to read.
392 */
393 literalMD = text:
394 if ! isString text then throw "literalMD expects a string."
395 else { _type = "literalMD"; inherit text; };
396
397 # Helper functions.
398
399 /* Convert an option, described as a list of the option parts to a
400 human-readable version.
401
402 Example:
403 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
404 (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
405 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable"
406
407 Placeholders will not be quoted as they are not actual values:
408 (showOption ["foo" "*" "bar"]) == "foo.*.bar"
409 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
410 */
411 showOption = parts: let
412 escapeOptionPart = part:
413 let
414 # We assume that these are "special values" and not real configuration data.
415 # If it is real configuration data, it is rendered incorrectly.
416 specialIdentifiers = [
417 "<name>" # attrsOf (submodule {})
418 "*" # listOf (submodule {})
419 "<function body>" # functionTo
420 ];
421 in if builtins.elem part specialIdentifiers
422 then part
423 else lib.strings.escapeNixIdentifier part;
424 in (concatStringsSep ".") (map escapeOptionPart parts);
425 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
426
427 showDefs = defs: concatMapStrings (def:
428 let
429 # Pretty print the value for display, if successful
430 prettyEval = builtins.tryEval
431 (lib.generators.toPretty { }
432 (lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value));
433 # Split it into its lines
434 lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value);
435 # Only display the first 5 lines, and indent them for better visibility
436 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
437 result =
438 # Don't print any value if evaluating the value strictly fails
439 if ! prettyEval.success then ""
440 # Put it on a new line if it consists of multiple
441 else if length lines > 1 then ":\n " + value
442 else ": " + value;
443 in "\n- In `${def.file}'${result}"
444 ) defs;
445
446 showOptionWithDefLocs = opt: ''
447 ${showOption opt.loc}, with values defined in:
448 ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files}
449 '';
450
451 unknownModule = "<unknown-file>";
452
453}