nanopb: include C sources (#91055)
[NixPkgs.git] / lib / options.nix
blob38f4f1329f2127f6362ec870e254e13e626903a6
1 # Nixpkgs/NixOS option handling.
2 { lib }:
4 with lib.trivial;
5 with lib.lists;
6 with lib.attrsets;
7 with lib.strings;
9 rec {
11   /* Returns true when the given argument is an option
13      Type: isOption :: a -> bool
15      Example:
16        isOption 1             // => false
17        isOption (mkOption {}) // => true
18   */
19   isOption = lib.isType "option";
21   /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
23      All keys default to `null` when not given.
25      Example:
26        mkOption { }  // => { _type = "option"; }
27        mkOption { defaultText = "foo"; } // => { _type = "option"; defaultText = "foo"; }
28   */
29   mkOption =
30     {
31     # Default value used when no definition is given in the configuration.
32     default ? null,
33     # Textual representation of the default, for the manual.
34     defaultText ? null,
35     # Example value used in the manual.
36     example ? null,
37     # String describing the option.
38     description ? null,
39     # Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix).
40     relatedPackages ? null,
41     # Option type, providing type-checking and value merging.
42     type ? null,
43     # Function that converts the option value to something else.
44     apply ? null,
45     # Whether the option is for NixOS developers only.
46     internal ? null,
47     # Whether the option shows up in the manual.
48     visible ? null,
49     # Whether the option can be set only once
50     readOnly ? null,
51     # Deprecated, used by types.optionSet.
52     options ? null
53     } @ attrs:
54     attrs // { _type = "option"; };
56   /* Creates an Option attribute set for a boolean value option i.e an
57      option to be toggled on or off:
59      Example:
60        mkEnableOption "foo"
61        => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; }
62   */
63   mkEnableOption =
64     # Name for the created option
65     name: mkOption {
66     default = false;
67     example = true;
68     description = "Whether to enable ${name}.";
69     type = lib.types.bool;
70   };
72   /* This option accepts anything, but it does not produce any result.
74      This is useful for sharing a module across different module sets
75      without having to implement similar features as long as the
76      values of the options are not accessed. */
77   mkSinkUndeclaredOptions = attrs: mkOption ({
78     internal = true;
79     visible = false;
80     default = false;
81     description = "Sink for option definitions.";
82     type = mkOptionType {
83       name = "sink";
84       check = x: true;
85       merge = loc: defs: false;
86     };
87     apply = x: throw "Option value is not readable because the option is not declared.";
88   } // attrs);
90   mergeDefaultOption = loc: defs:
91     let list = getValues defs; in
92     if length list == 1 then head list
93     else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list)
94     else if all isList list then concatLists list
95     else if all isAttrs list then foldl' lib.mergeAttrs {} list
96     else if all isBool list then foldl' lib.or false list
97     else if all isString list then lib.concatStrings list
98     else if all isInt list && all (x: x == head list) list then head list
99     else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
101   mergeOneOption = loc: defs:
102     if defs == [] then abort "This case should never happen."
103     else if length defs != 1 then
104       throw "The unique option `${showOption loc}' is defined multiple times, in:\n - ${concatStringsSep "\n - " (getFiles defs)}."
105     else (head defs).value;
107   /* "Merge" option definitions by checking that they all have the same value. */
108   mergeEqualOption = loc: defs:
109     if defs == [] then abort "This case should never happen."
110     else foldl' (val: def:
111       if def.value != val then
112         throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
113       else
114         val) (head defs).value defs;
116   /* Extracts values of all "value" keys of the given list.
118      Type: getValues :: [ { value :: a } ] -> [a]
120      Example:
121        getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
122        getValues [ ]                               // => [ ]
123   */
124   getValues = map (x: x.value);
126   /* Extracts values of all "file" keys of the given list
128      Type: getFiles :: [ { file :: a } ] -> [a]
130      Example:
131        getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
132        getFiles [ ]                                         // => [ ]
133   */
134   getFiles = map (x: x.file);
136   # Generate documentation template from the list of option declaration like
137   # the set generated with filterOptionSets.
138   optionAttrSetToDocList = optionAttrSetToDocList' [];
140   optionAttrSetToDocList' = prefix: options:
141     concatMap (opt:
142       let
143         docOption = rec {
144           loc = opt.loc;
145           name = showOption opt.loc;
146           description = opt.description or (lib.warn "Option `${name}' has no description." "This option has no description.");
147           declarations = filter (x: x != unknownModule) opt.declarations;
148           internal = opt.internal or false;
149           visible = opt.visible or true;
150           readOnly = opt.readOnly or false;
151           type = opt.type.description or null;
152         }
153         // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
154         // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
155         // optionalAttrs (opt ? defaultText) { default = opt.defaultText; }
156         // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; };
158         subOptions =
159           let ss = opt.type.getSubOptions opt.loc;
160           in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
161       in
162         [ docOption ] ++ optionals docOption.visible subOptions) (collect isOption options);
165   /* This function recursively removes all derivation attributes from
166      `x` except for the `name` attribute.
168      This is to make the generation of `options.xml` much more
169      efficient: the XML representation of derivations is very large
170      (on the order of megabytes) and is not actually used by the
171      manual generator.
172   */
173   scrubOptionValue = x:
174     if isDerivation x then
175       { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
176     else if isList x then map scrubOptionValue x
177     else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
178     else x;
181   /* For use in the `example` option attribute. It causes the given
182      text to be included verbatim in documentation. This is necessary
183      for example values that are not simple values, e.g., functions.
184   */
185   literalExample = text: { _type = "literalExample"; inherit text; };
187   # Helper functions.
189   /* Convert an option, described as a list of the option parts in to a
190      safe, human readable version.
192      Example:
193        (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
194        (showOption ["foo" "bar.baz" "tux"]) == "foo.bar.baz.tux"
196      Placeholders will not be quoted as they are not actual values:
197        (showOption ["foo" "*" "bar"]) == "foo.*.bar"
198        (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
200      Unlike attributes, options can also start with numbers:
201        (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.2bwm.enable"
202   */
203   showOption = parts: let
204     escapeOptionPart = part:
205       let
206         escaped = lib.strings.escapeNixString part;
207       in if escaped == "\"${part}\""
208          then part
209          else escaped;
210     in (concatStringsSep ".") (map escapeOptionPart parts);
211   showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
212   unknownModule = "<unknown-file>";