at 24.11-pre 19 kB view raw
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 /* 258 Require a single definition. 259 260 WARNING: Does not perform nested checks, as this does not run the merge function! 261 */ 262 mergeOneOption = mergeUniqueOption { message = ""; }; 263 264 /* 265 Require a single definition. 266 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}"; 282 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; 295 296 /* Extracts values of all "value" keys of the given list. 297 298 Type: getValues :: [ { value :: a; } ] -> [a] 299 300 Example: 301 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ] 302 getValues [ ] // => [ ] 303 */ 304 getValues = map (x: x.value); 305 306 /* Extracts values of all "file" keys of the given list 307 308 Type: getFiles :: [ { file :: a; } ] -> [a] 309 310 Example: 311 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ] 312 getFiles [ ] // => [ ] 313 */ 314 getFiles = map (x: x.file); 315 316 # Generate documentation template from the list of option declaration like 317 # the set generated with filterOptionSets. 318 optionAttrSetToDocList = optionAttrSetToDocList' []; 319 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; }; 350 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); 359 360 361 /* This function recursively removes all derivation attributes from 362 `x` except for the `name` attribute. 363 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. 368 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; 378 379 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); 389 390 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; }; 399 400 literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression; 401 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."; 408 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; }; 416 417 # Helper functions. 418 419 /* Convert an option, described as a list of the option parts to a 420 human-readable version. 421 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" 426 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); 446 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; 465 466 showOptionWithDefLocs = opt: '' 467 ${showOption opt.loc}, with values defined in: 468 ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files} 469 ''; 470 471 unknownModule = "<unknown-file>"; 472 473}