at master 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, 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}