Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at github-to-sqlite-beautifulsoup4 415 lines 16 kB view raw
1# Nixpkgs/NixOS option handling. 2{ lib }: 3 4let 5 inherit (lib) 6 all 7 collect 8 concatLists 9 concatMap 10 concatMapStringsSep 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 attrByPath 30 optionalAttrs 31 ; 32 inherit (lib.strings) 33 concatMapStrings 34 concatStringsSep 35 ; 36 inherit (lib.types) 37 mkOptionType 38 ; 39 inherit (lib.lists) 40 last 41 ; 42 prioritySuggestion = '' 43 Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions. 44 ''; 45in 46rec { 47 48 /* Returns true when the given argument is an option 49 50 Type: isOption :: a -> bool 51 52 Example: 53 isOption 1 // => false 54 isOption (mkOption {}) // => true 55 */ 56 isOption = lib.isType "option"; 57 58 /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys: 59 60 All keys default to `null` when not given. 61 62 Example: 63 mkOption { } // => { _type = "option"; } 64 mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; } 65 */ 66 mkOption = 67 { 68 # Default value used when no definition is given in the configuration. 69 default ? null, 70 # Textual representation of the default, for the manual. 71 defaultText ? null, 72 # Example value used in the manual. 73 example ? null, 74 # String describing the option. 75 description ? null, 76 # Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix). 77 relatedPackages ? null, 78 # Option type, providing type-checking and value merging. 79 type ? null, 80 # Function that converts the option value to something else. 81 apply ? null, 82 # Whether the option is for NixOS developers only. 83 internal ? null, 84 # 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. 85 visible ? null, 86 # Whether the option can be set only once 87 readOnly ? null, 88 } @ attrs: 89 attrs // { _type = "option"; }; 90 91 /* Creates an Option attribute set for a boolean value option i.e an 92 option to be toggled on or off: 93 94 Example: 95 mkEnableOption "foo" 96 => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; } 97 */ 98 mkEnableOption = 99 # Name for the created option 100 name: mkOption { 101 default = false; 102 example = true; 103 description = "Whether to enable ${name}."; 104 type = lib.types.bool; 105 }; 106 107 /* Creates an Option attribute set for an option that specifies the 108 package a module should use for some purpose. 109 110 The package is specified in the third argument under `default` as a list of strings 111 representing its attribute path in nixpkgs (or another package set). 112 Because of this, you need to pass nixpkgs itself (or a subset) as the first argument. 113 114 The second argument may be either a string or a list of strings. 115 It provides the display name of the package in the description of the generated option 116 (using only the last element if the passed value is a list) 117 and serves as the fallback value for the `default` argument. 118 119 To include extra information in the description, pass `extraDescription` to 120 append arbitrary text to the generated description. 121 You can also pass an `example` value, either a literal string or an attribute path. 122 123 The default argument can be omitted if the provided name is 124 an attribute of pkgs (if name is a string) or a 125 valid attribute path in pkgs (if name is a list). 126 127 If you wish to explicitly provide no default, pass `null` as `default`. 128 129 Type: mkPackageOption :: pkgs -> (string|[string]) -> { default? :: [string], example? :: null|string|[string], extraDescription? :: string } -> option 130 131 Example: 132 mkPackageOption pkgs "hello" { } 133 => { _type = "option"; default = «derivation /nix/store/3r2vg51hlxj3cx5vscp0vkv60bqxkaq0-hello-2.10.drv»; defaultText = { ... }; description = "The hello package to use."; type = { ... }; } 134 135 Example: 136 mkPackageOption pkgs "GHC" { 137 default = [ "ghc" ]; 138 example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; 139 } 140 => { _type = "option"; default = «derivation /nix/store/jxx55cxsjrf8kyh3fp2ya17q99w7541r-ghc-8.10.7.drv»; defaultText = { ... }; description = "The GHC package to use."; example = { ... }; type = { ... }; } 141 142 Example: 143 mkPackageOption pkgs [ "python39Packages" "pytorch" ] { 144 extraDescription = "This is an example and doesn't actually do anything."; 145 } 146 => { _type = "option"; default = «derivation /nix/store/gvqgsnc4fif9whvwd9ppa568yxbkmvk8-python3.9-pytorch-1.10.2.drv»; defaultText = { ... }; description = "The pytorch package to use. This is an example and doesn't actually do anything."; type = { ... }; } 147 148 */ 149 mkPackageOption = 150 # Package set (a specific version of nixpkgs or a subset) 151 pkgs: 152 # Name for the package, shown in option description 153 name: 154 { 155 # Whether the package can be null, for example to disable installing a package altogether. 156 nullable ? false, 157 # The attribute path where the default package is located (may be omitted) 158 default ? name, 159 # A string or an attribute path to use as an example (may be omitted) 160 example ? null, 161 # Additional text to include in the option description (may be omitted) 162 extraDescription ? "", 163 }: 164 let 165 name' = if isList name then last name else name; 166 in mkOption ({ 167 type = with lib.types; (if nullable then nullOr else lib.id) package; 168 description = "The ${name'} package to use." 169 + (if extraDescription == "" then "" else " ") + extraDescription; 170 } // (if default != null then let 171 default' = if isList default then default else [ default ]; 172 defaultPath = concatStringsSep "." default'; 173 defaultValue = attrByPath default' 174 (throw "${defaultPath} cannot be found in pkgs") pkgs; 175 in { 176 default = defaultValue; 177 defaultText = literalExpression ("pkgs." + defaultPath); 178 } else if nullable then { 179 default = null; 180 } else { }) // lib.optionalAttrs (example != null) { 181 example = literalExpression 182 (if isList example then "pkgs." + concatStringsSep "." example else example); 183 }); 184 185 /* Alias of mkPackageOption. Previously used to create options with markdown 186 documentation, which is no longer required. 187 */ 188 mkPackageOptionMD = mkPackageOption; 189 190 /* This option accepts anything, but it does not produce any result. 191 192 This is useful for sharing a module across different module sets 193 without having to implement similar features as long as the 194 values of the options are not accessed. */ 195 mkSinkUndeclaredOptions = attrs: mkOption ({ 196 internal = true; 197 visible = false; 198 default = false; 199 description = "Sink for option definitions."; 200 type = mkOptionType { 201 name = "sink"; 202 check = x: true; 203 merge = loc: defs: false; 204 }; 205 apply = x: throw "Option value is not readable because the option is not declared."; 206 } // attrs); 207 208 mergeDefaultOption = loc: defs: 209 let list = getValues defs; in 210 if length list == 1 then head list 211 else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list) 212 else if all isList list then concatLists list 213 else if all isAttrs list then foldl' lib.mergeAttrs {} list 214 else if all isBool list then foldl' lib.or false list 215 else if all isString list then lib.concatStrings list 216 else if all isInt list && all (x: x == head list) list then head list 217 else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; 218 219 mergeOneOption = mergeUniqueOption { message = ""; }; 220 221 mergeUniqueOption = { message }: loc: defs: 222 if length defs == 1 223 then (head defs).value 224 else assert length defs > 1; 225 throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}"; 226 227 /* "Merge" option definitions by checking that they all have the same value. */ 228 mergeEqualOption = loc: defs: 229 if defs == [] then abort "This case should never happen." 230 # Return early if we only have one element 231 # This also makes it work for functions, because the foldl' below would try 232 # to compare the first element with itself, which is false for functions 233 else if length defs == 1 then (head defs).value 234 else (foldl' (first: def: 235 if def.value != first.value then 236 throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}\n${prioritySuggestion}" 237 else 238 first) (head defs) (tail defs)).value; 239 240 /* Extracts values of all "value" keys of the given list. 241 242 Type: getValues :: [ { value :: a; } ] -> [a] 243 244 Example: 245 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ] 246 getValues [ ] // => [ ] 247 */ 248 getValues = map (x: x.value); 249 250 /* Extracts values of all "file" keys of the given list 251 252 Type: getFiles :: [ { file :: a; } ] -> [a] 253 254 Example: 255 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ] 256 getFiles [ ] // => [ ] 257 */ 258 getFiles = map (x: x.file); 259 260 # Generate documentation template from the list of option declaration like 261 # the set generated with filterOptionSets. 262 optionAttrSetToDocList = optionAttrSetToDocList' []; 263 264 optionAttrSetToDocList' = _: options: 265 concatMap (opt: 266 let 267 name = showOption opt.loc; 268 docOption = { 269 loc = opt.loc; 270 inherit name; 271 description = opt.description or null; 272 declarations = filter (x: x != unknownModule) opt.declarations; 273 internal = opt.internal or false; 274 visible = 275 if (opt?visible && opt.visible == "shallow") 276 then true 277 else opt.visible or true; 278 readOnly = opt.readOnly or false; 279 type = opt.type.description or "unspecified"; 280 } 281 // optionalAttrs (opt ? example) { 282 example = 283 builtins.addErrorContext "while evaluating the example of option `${name}`" ( 284 renderOptionValue opt.example 285 ); 286 } 287 // optionalAttrs (opt ? defaultText || opt ? default) { 288 default = 289 builtins.addErrorContext "while evaluating the ${if opt?defaultText then "defaultText" else "default value"} of option `${name}`" ( 290 renderOptionValue (opt.defaultText or opt.default) 291 ); 292 } 293 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; 294 295 subOptions = 296 let ss = opt.type.getSubOptions opt.loc; 297 in if ss != {} then optionAttrSetToDocList' opt.loc ss else []; 298 subOptionsVisible = docOption.visible && opt.visible or null != "shallow"; 299 in 300 # To find infinite recursion in NixOS option docs: 301 # builtins.trace opt.loc 302 [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options); 303 304 305 /* This function recursively removes all derivation attributes from 306 `x` except for the `name` attribute. 307 308 This is to make the generation of `options.xml` much more 309 efficient: the XML representation of derivations is very large 310 (on the order of megabytes) and is not actually used by the 311 manual generator. 312 313 This function was made obsolete by renderOptionValue and is kept for 314 compatibility with out-of-tree code. 315 */ 316 scrubOptionValue = x: 317 if isDerivation x then 318 { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; } 319 else if isList x then map scrubOptionValue x 320 else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"]) 321 else x; 322 323 324 /* Ensures that the given option value (default or example) is a `_type`d string 325 by rendering Nix values to `literalExpression`s. 326 */ 327 renderOptionValue = v: 328 if v ? _type && v ? text then v 329 else literalExpression (lib.generators.toPretty { 330 multiline = true; 331 allowPrettyValues = true; 332 } v); 333 334 335 /* For use in the `defaultText` and `example` option attributes. Causes the 336 given string to be rendered verbatim in the documentation as Nix code. This 337 is necessary for complex values, e.g. functions, or values that depend on 338 other values or packages. 339 */ 340 literalExpression = text: 341 if ! isString text then throw "literalExpression expects a string." 342 else { _type = "literalExpression"; inherit text; }; 343 344 literalExample = lib.warn "literalExample is deprecated, use literalExpression instead, or use literalMD for a non-Nix description." literalExpression; 345 346 /* Transition marker for documentation that's already migrated to markdown 347 syntax. This is a no-op and no longer needed. 348 */ 349 mdDoc = lib.id; 350 351 /* For use in the `defaultText` and `example` option attributes. Causes the 352 given MD text to be inserted verbatim in the documentation, for when 353 a `literalExpression` would be too hard to read. 354 */ 355 literalMD = text: 356 if ! isString text then throw "literalMD expects a string." 357 else { _type = "literalMD"; inherit text; }; 358 359 # Helper functions. 360 361 /* Convert an option, described as a list of the option parts to a 362 human-readable version. 363 364 Example: 365 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz" 366 (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux" 367 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable" 368 369 Placeholders will not be quoted as they are not actual values: 370 (showOption ["foo" "*" "bar"]) == "foo.*.bar" 371 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar" 372 */ 373 showOption = parts: let 374 escapeOptionPart = part: 375 let 376 # We assume that these are "special values" and not real configuration data. 377 # If it is real configuration data, it is rendered incorrectly. 378 specialIdentifiers = [ 379 "<name>" # attrsOf (submodule {}) 380 "*" # listOf (submodule {}) 381 "<function body>" # functionTo 382 ]; 383 in if builtins.elem part specialIdentifiers 384 then part 385 else lib.strings.escapeNixIdentifier part; 386 in (concatStringsSep ".") (map escapeOptionPart parts); 387 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); 388 389 showDefs = defs: concatMapStrings (def: 390 let 391 # Pretty print the value for display, if successful 392 prettyEval = builtins.tryEval 393 (lib.generators.toPretty { } 394 (lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value)); 395 # Split it into its lines 396 lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value); 397 # Only display the first 5 lines, and indent them for better visibility 398 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "..."); 399 result = 400 # Don't print any value if evaluating the value strictly fails 401 if ! prettyEval.success then "" 402 # Put it on a new line if it consists of multiple 403 else if length lines > 1 then ":\n " + value 404 else ": " + value; 405 in "\n- In `${def.file}'${result}" 406 ) defs; 407 408 showOptionWithDefLocs = opt: '' 409 ${showOption opt.loc}, with values defined in: 410 ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files} 411 ''; 412 413 unknownModule = "<unknown-file>"; 414 415}