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