diff --git a/lib/options.nix b/lib/options.nix index 53001a3113f9..794ca5e33942 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -26,6 +26,7 @@ let take ; inherit (lib.attrsets) + attrByPath optionalAttrs ; inherit (lib.strings) @@ -99,6 +100,49 @@ rec { type = lib.types.bool; }; + /* Creates an Option attribute set for an option that specifies the + package a module should use for some purpose. + + Type: mkPackageOption :: pkgs -> string -> { default :: [string], example :: null | string | [string] } -> option + + The package is specified as a list of strings representing its attribute path in nixpkgs. + + Because of this, you need to pass nixpkgs itself as the first argument. + + The second argument is the name of the option, used in the description "The package to use.". + + You can also pass an example value, either a literal string or a package's attribute path. + + You can omit the default path if the name of the option is also attribute path in nixpkgs. + + Example: + mkPackageOption pkgs "hello" { } + => { _type = "option"; default = «derivation /nix/store/3r2vg51hlxj3cx5vscp0vkv60bqxkaq0-hello-2.10.drv»; defaultText = { ... }; description = "The hello package to use."; type = { ... }; } + + Example: + mkPackageOption pkgs "GHC" { + default = [ "ghc" ]; + example = "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])"; + } + => { _type = "option"; default = «derivation /nix/store/jxx55cxsjrf8kyh3fp2ya17q99w7541r-ghc-8.10.7.drv»; defaultText = { ... }; description = "The GHC package to use."; example = { ... }; type = { ... }; } + */ + mkPackageOption = + # Package set (a specific version of nixpkgs) + pkgs: + # Name for the package, shown in option description + name: + { default ? [ name ], example ? null }: + let default' = if !isList default then [ default ] else default; + in mkOption { + type = lib.types.package; + description = "The ${name} package to use."; + default = attrByPath default' + (throw "${concatStringsSep "." default'} cannot be found in pkgs") pkgs; + defaultText = literalExpression ("pkgs." + concatStringsSep "." default'); + ${if example != null then "example" else null} = literalExpression + (if isList example then "pkgs." + concatStringsSep "." example else example); + }; + /* This option accepts anything, but it does not produce any result. This is useful for sharing a module across different module sets diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md index be56529992ab..fff06e1ea5ba 100644 --- a/nixos/doc/manual/development/option-declarations.section.md +++ b/nixos/doc/manual/development/option-declarations.section.md @@ -57,6 +57,80 @@ The function `mkOption` accepts the following arguments. : A textual description of the option, in DocBook format, that will be included in the NixOS manual. +## Utility functions for common option patterns {#sec-option-declarations-util} + +### `mkEnableOption` {#sec-option-declarations-util-mkEnableOption} + +Creates an Option attribute set for a boolean value option i.e an +option to be toggled on or off. + +This function takes a single string argument, the name of the thing to be toggled. + +The option's description is "Whether to enable \.". + +For example: + +::: {#ex-options-declarations-util-mkEnableOption-magic .example} +```nix +lib.mkEnableOption "magic" +# is like +lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether to enable magic."; +} +``` + +### `mkPackageOption` {#sec-option-declarations-util-mkPackageOption} + +Usage: + +```nix +mkPackageOption pkgs "name" { default = [ "path" "in" "pkgs" ]; example = "literal example"; } +``` + +Creates an Option attribute set for an option that specifies the package a module should use for some purpose. + +**Note**: You shouldn’t necessarily make package options for all of your modules. You can always overwrite a specific package throughout nixpkgs by using [nixpkgs overlays](https://nixos.org/manual/nixpkgs/stable/#chap-overlays). + +The default package is specified as a list of strings representing its attribute path in nixpkgs. Because of this, you need to pass nixpkgs itself as the first argument. + +The second argument is the name of the option, used in the description "The \ package to use.". You can also pass an example value, either a literal string or a package's attribute path. + +You can omit the default path if the name of the option is also attribute path in nixpkgs. + +::: {#ex-options-declarations-util-mkPackageOption .title} +Examples: + +::: {#ex-options-declarations-util-mkPackageOption-hello .example} +```nix +lib.mkPackageOption pkgs "hello" { } +# is like +lib.mkOption { + type = lib.types.package; + default = pkgs.hello; + defaultText = lib.literalExpression "pkgs.hello"; + description = "The hello package to use."; +} +``` + +::: {#ex-options-declarations-util-mkPackageOption-ghc .example} +```nix +lib.mkPackageOption pkgs "GHC" { + default = [ "ghc" ]; + example = "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])"; +} +# is like +lib.mkOption { + type = lib.types.package; + default = pkgs.ghc; + defaultText = lib.literalExpression "pkgs.ghc"; + example = lib.literalExpression "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])"; + description = "The GHC package to use."; +} +``` + ## Extensible Option Types {#sec-option-declarations-eot} Extensible option types is a feature that allow to extend certain types diff --git a/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixos/doc/manual/from_md/development/option-declarations.section.xml index 2845e37659b1..0eeffae628e1 100644 --- a/nixos/doc/manual/from_md/development/option-declarations.section.xml +++ b/nixos/doc/manual/from_md/development/option-declarations.section.xml @@ -97,125 +97,228 @@ options = { -
- Extensible Option Types - - Extensible option types is a feature that allow to extend certain - types declaration through multiple module files. This feature only - work with a restricted set of types, namely - enum and submodules and any - composed forms of them. - - - Extensible option types can be used for enum - options that affects multiple modules, or as an alternative to - related enable options. - - - As an example, we will take the case of display managers. There is - a central display manager module for generic display manager - options and a module file per display manager backend (sddm, gdm - ...). - - - There are two approach to this module structure: - - - +
+ Utility functions for common option patterns +
+ <literal>mkEnableOption</literal> + + Creates an Option attribute set for a boolean value option i.e + an option to be toggled on or off. + + + This function takes a single string argument, the name of the + thing to be toggled. + + + The option’s description is Whether to enable + <name>.. + + + For example: + + + +lib.mkEnableOption "magic" +# is like +lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether to enable magic."; +} + +
+ <literal>mkPackageOption</literal> - Managing the display managers independently by adding an - enable option to every display manager module backend. (NixOS) + Usage: - - + +mkPackageOption pkgs "name" { default = [ "path" "in" "pkgs" ]; example = "literal example"; } + - Managing the display managers in the central module by adding - an option to select which display manager backend to use. + Creates an Option attribute set for an option that specifies + the package a module should use for some purpose. - - - - Both approaches have problems. - - - Making backends independent can quickly become hard to manage. For - display managers, there can be only one enabled at a time, but the - type system can not enforce this restriction as there is no - relation between each backend enable option. As - a result, this restriction has to be done explicitely by adding - assertions in each display manager backend module. - - - On the other hand, managing the display managers backends in the - central module will require to change the central module option - every time a new backend is added or removed. - - - By using extensible option types, it is possible to create a - placeholder option in the central module - (Example: - Extensible type placeholder in the service module), and to - extend it in each backend module - (Example: - Extending - services.xserver.displayManager.enable in the - gdm module, - Example: - Extending - services.xserver.displayManager.enable in the - sddm module). - - - As a result, displayManager.enable option - values can be added without changing the main service module file - and the type system automatically enforce that there can only be a - single display manager enabled. - - - - Example: Extensible type placeholder in - the service module - - + + Note: You shouldn’t + necessarily make package options for all of your modules. You + can always overwrite a specific package throughout nixpkgs by + using + nixpkgs + overlays. + + + The default package is specified as a list of strings + representing its attribute path in nixpkgs. Because of this, + you need to pass nixpkgs itself as the first argument. + + + The second argument is the name of the option, used in the + description The <name> package to use.. + You can also pass an example value, either a literal string or + a package’s attribute path. + + + You can omit the default path if the name of the option is + also attribute path in nixpkgs. + + + + Examples: + + + +lib.mkPackageOption pkgs "hello" { } +# is like +lib.mkOption { + type = lib.types.package; + default = pkgs.hello; + defaultText = lib.literalExpression "pkgs.hello"; + description = "The hello package to use."; +} + + + +lib.mkPackageOption pkgs "GHC" { + default = [ "ghc" ]; + example = "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])"; +} +# is like +lib.mkOption { + type = lib.types.package; + default = pkgs.ghc; + defaultText = lib.literalExpression "pkgs.ghc"; + example = lib.literalExpression "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])"; + description = "The GHC package to use."; +} + +
+ Extensible Option Types + + Extensible option types is a feature that allow to extend + certain types declaration through multiple module files. + This feature only work with a restricted set of types, + namely enum and + submodules and any composed forms of + them. + + + Extensible option types can be used for + enum options that affects multiple + modules, or as an alternative to related + enable options. + + + As an example, we will take the case of display managers. + There is a central display manager module for generic + display manager options and a module file per display + manager backend (sddm, gdm ...). + + + There are two approach to this module structure: + + + + + Managing the display managers independently by adding an + enable option to every display manager module backend. + (NixOS) + + + + + Managing the display managers in the central module by + adding an option to select which display manager backend + to use. + + + + + Both approaches have problems. + + + Making backends independent can quickly become hard to + manage. For display managers, there can be only one enabled + at a time, but the type system can not enforce this + restriction as there is no relation between each backend + enable option. As a result, this + restriction has to be done explicitely by adding assertions + in each display manager backend module. + + + On the other hand, managing the display managers backends in + the central module will require to change the central module + option every time a new backend is added or removed. + + + By using extensible option types, it is possible to create a + placeholder option in the central module + (Example: + Extensible type placeholder in the service module), + and to extend it in each backend module + (Example: + Extending + services.xserver.displayManager.enable in + the gdm module, + Example: + Extending + services.xserver.displayManager.enable in + the sddm module). + + + As a result, displayManager.enable option + values can be added without changing the main service module + file and the type system automatically enforce that there + can only be a single display manager enabled. + + + + Example: Extensible type placeholder + in the service module + + services.xserver.displayManager.enable = mkOption { description = "Display manager to use"; type = with types; nullOr (enum [ ]); }; - - - Example: Extending - services.xserver.displayManager.enable in the - gdm module - - + + + Example: Extending + services.xserver.displayManager.enable in + the gdm module + + services.xserver.displayManager.enable = mkOption { type = with types; nullOr (enum [ "gdm" ]); }; - - - Example: Extending - services.xserver.displayManager.enable in the - sddm module - - + + + Example: Extending + services.xserver.displayManager.enable in + the sddm module + + services.xserver.displayManager.enable = mkOption { type = with types; nullOr (enum [ "sddm" ]); }; - - The placeholder declaration is a standard - mkOption declaration, but it is important that - extensible option declarations only use the - type argument. - - - Extensible option types work with any of the composed variants of - enum such as - with types; nullOr (enum [ "foo" "bar" ]) - or - with types; listOf (enum [ "foo" "bar" ]). - + + The placeholder declaration is a standard + mkOption declaration, but it is important + that extensible option declarations only use the + type argument. + + + Extensible option types work with any of the composed + variants of enum such as + with types; nullOr (enum [ "foo" "bar" ]) + or + with types; listOf (enum [ "foo" "bar" ]). + +
+
+