at 22.05-pre 10 kB view raw
1# Nixpkgs/NixOS option handling. 2{ lib }: 3 4let 5 inherit (lib) 6 all 7 collect 8 concatLists 9 concatMap 10 elemAt 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 optionalAttrs 30 ; 31 inherit (lib.strings) 32 concatMapStrings 33 concatStringsSep 34 ; 35 inherit (lib.types) 36 mkOptionType 37 ; 38in 39rec { 40 41 /* Returns true when the given argument is an option 42 43 Type: isOption :: a -> bool 44 45 Example: 46 isOption 1 // => false 47 isOption (mkOption {}) // => true 48 */ 49 isOption = lib.isType "option"; 50 51 /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys: 52 53 All keys default to `null` when not given. 54 55 Example: 56 mkOption { } // => { _type = "option"; } 57 mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; } 58 */ 59 mkOption = 60 { 61 # Default value used when no definition is given in the configuration. 62 default ? null, 63 # Textual representation of the default, for the manual. 64 defaultText ? null, 65 # Example value used in the manual. 66 example ? null, 67 # String describing the option. 68 description ? null, 69 # Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix). 70 relatedPackages ? null, 71 # Option type, providing type-checking and value merging. 72 type ? null, 73 # Function that converts the option value to something else. 74 apply ? null, 75 # Whether the option is for NixOS developers only. 76 internal ? null, 77 # 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. 78 visible ? null, 79 # Whether the option can be set only once 80 readOnly ? null, 81 # Deprecated, used by types.optionSet. 82 options ? null 83 } @ attrs: 84 attrs // { _type = "option"; }; 85 86 /* Creates an Option attribute set for a boolean value option i.e an 87 option to be toggled on or off: 88 89 Example: 90 mkEnableOption "foo" 91 => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; } 92 */ 93 mkEnableOption = 94 # Name for the created option 95 name: mkOption { 96 default = false; 97 example = true; 98 description = "Whether to enable ${name}."; 99 type = lib.types.bool; 100 }; 101 102 /* This option accepts anything, but it does not produce any result. 103 104 This is useful for sharing a module across different module sets 105 without having to implement similar features as long as the 106 values of the options are not accessed. */ 107 mkSinkUndeclaredOptions = attrs: mkOption ({ 108 internal = true; 109 visible = false; 110 default = false; 111 description = "Sink for option definitions."; 112 type = mkOptionType { 113 name = "sink"; 114 check = x: true; 115 merge = loc: defs: false; 116 }; 117 apply = x: throw "Option value is not readable because the option is not declared."; 118 } // attrs); 119 120 mergeDefaultOption = loc: defs: 121 let list = getValues defs; in 122 if length list == 1 then head list 123 else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list) 124 else if all isList list then concatLists list 125 else if all isAttrs list then foldl' lib.mergeAttrs {} list 126 else if all isBool list then foldl' lib.or false list 127 else if all isString list then lib.concatStrings list 128 else if all isInt list && all (x: x == head list) list then head list 129 else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; 130 131 mergeOneOption = loc: defs: 132 if defs == [] then abort "This case should never happen." 133 else if length defs != 1 then 134 throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}" 135 else (head defs).value; 136 137 /* "Merge" option definitions by checking that they all have the same value. */ 138 mergeEqualOption = loc: defs: 139 if defs == [] then abort "This case should never happen." 140 # Return early if we only have one element 141 # This also makes it work for functions, because the foldl' below would try 142 # to compare the first element with itself, which is false for functions 143 else if length defs == 1 then (head defs).value 144 else (foldl' (first: def: 145 if def.value != first.value then 146 throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}" 147 else 148 first) (head defs) (tail defs)).value; 149 150 /* Extracts values of all "value" keys of the given list. 151 152 Type: getValues :: [ { value :: a } ] -> [a] 153 154 Example: 155 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ] 156 getValues [ ] // => [ ] 157 */ 158 getValues = map (x: x.value); 159 160 /* Extracts values of all "file" keys of the given list 161 162 Type: getFiles :: [ { file :: a } ] -> [a] 163 164 Example: 165 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ] 166 getFiles [ ] // => [ ] 167 */ 168 getFiles = map (x: x.file); 169 170 # Generate documentation template from the list of option declaration like 171 # the set generated with filterOptionSets. 172 optionAttrSetToDocList = optionAttrSetToDocList' []; 173 174 optionAttrSetToDocList' = prefix: options: 175 concatMap (opt: 176 let 177 docOption = rec { 178 loc = opt.loc; 179 name = showOption opt.loc; 180 description = opt.description or (lib.warn "Option `${name}' has no description." "This option has no description."); 181 declarations = filter (x: x != unknownModule) opt.declarations; 182 internal = opt.internal or false; 183 visible = 184 if (opt?visible && opt.visible == "shallow") 185 then true 186 else opt.visible or true; 187 readOnly = opt.readOnly or false; 188 type = opt.type.description or null; 189 } 190 // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; } 191 // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; } 192 // optionalAttrs (opt ? defaultText) { default = opt.defaultText; } 193 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; 194 195 subOptions = 196 let ss = opt.type.getSubOptions opt.loc; 197 in if ss != {} then optionAttrSetToDocList' opt.loc ss else []; 198 subOptionsVisible = docOption.visible && opt.visible or null != "shallow"; 199 in 200 [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options); 201 202 203 /* This function recursively removes all derivation attributes from 204 `x` except for the `name` attribute. 205 206 This is to make the generation of `options.xml` much more 207 efficient: the XML representation of derivations is very large 208 (on the order of megabytes) and is not actually used by the 209 manual generator. 210 */ 211 scrubOptionValue = x: 212 if isDerivation x then 213 { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; } 214 else if isList x then map scrubOptionValue x 215 else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"]) 216 else x; 217 218 219 /* For use in the `defaultText` and `example` option attributes. Causes the 220 given string to be rendered verbatim in the documentation as Nix code. This 221 is necessary for complex values, e.g. functions, or values that depend on 222 other values or packages. 223 */ 224 literalExpression = text: 225 if ! isString text then throw "literalExpression expects a string." 226 else { _type = "literalExpression"; inherit text; }; 227 228 literalExample = lib.warn "literalExample is deprecated, use literalExpression instead, or use literalDocBook for a non-Nix description." literalExpression; 229 230 231 /* For use in the `defaultText` and `example` option attributes. Causes the 232 given DocBook text to be inserted verbatim in the documentation, for when 233 a `literalExpression` would be too hard to read. 234 */ 235 literalDocBook = text: 236 if ! isString text then throw "literalDocBook expects a string." 237 else { _type = "literalDocBook"; inherit text; }; 238 239 # Helper functions. 240 241 /* Convert an option, described as a list of the option parts in to a 242 safe, human readable version. 243 244 Example: 245 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz" 246 (showOption ["foo" "bar.baz" "tux"]) == "foo.bar.baz.tux" 247 248 Placeholders will not be quoted as they are not actual values: 249 (showOption ["foo" "*" "bar"]) == "foo.*.bar" 250 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar" 251 252 Unlike attributes, options can also start with numbers: 253 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.2bwm.enable" 254 */ 255 showOption = parts: let 256 escapeOptionPart = part: 257 let 258 escaped = lib.strings.escapeNixString part; 259 in if escaped == "\"${part}\"" 260 then part 261 else escaped; 262 in (concatStringsSep ".") (map escapeOptionPart parts); 263 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); 264 265 showDefs = defs: concatMapStrings (def: 266 let 267 # Pretty print the value for display, if successful 268 prettyEval = builtins.tryEval 269 (lib.generators.toPretty { } 270 (lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value)); 271 # Split it into its lines 272 lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value); 273 # Only display the first 5 lines, and indent them for better visibility 274 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "..."); 275 result = 276 # Don't print any value if evaluating the value strictly fails 277 if ! prettyEval.success then "" 278 # Put it on a new line if it consists of multiple 279 else if length lines > 1 then ":\n " + value 280 else ": " + value; 281 in "\n- In `${def.file}'${result}" 282 ) defs; 283 284 unknownModule = "<unknown-file>"; 285 286}