at 18.03-beta 5.5 kB view raw
1# Nixpkgs/NixOS option handling. 2{ lib }: 3 4with lib.trivial; 5with lib.lists; 6with lib.attrsets; 7with lib.strings; 8 9rec { 10 11 isOption = lib.isType "option"; 12 mkOption = 13 { default ? null # Default value used when no definition is given in the configuration. 14 , defaultText ? null # Textual representation of the default, for in the manual. 15 , example ? null # Example value used in the manual. 16 , description ? null # String describing the option. 17 , relatedPackages ? null # Related packages used in the manual (see `genRelatedPackages` in ../nixos/doc/manual/default.nix). 18 , type ? null # Option type, providing type-checking and value merging. 19 , apply ? null # Function that converts the option value to something else. 20 , internal ? null # Whether the option is for NixOS developers only. 21 , visible ? null # Whether the option shows up in the manual. 22 , readOnly ? null # Whether the option can be set only once 23 , options ? null # Obsolete, used by types.optionSet. 24 } @ attrs: 25 attrs // { _type = "option"; }; 26 27 mkEnableOption = name: mkOption { 28 default = false; 29 example = true; 30 description = "Whether to enable ${name}."; 31 type = lib.types.bool; 32 }; 33 34 # This option accept anything, but it does not produce any result. This 35 # is useful for sharing a module across different module sets without 36 # having to implement similar features as long as the value of the options 37 # are not expected. 38 mkSinkUndeclaredOptions = attrs: mkOption ({ 39 internal = true; 40 visible = false; 41 default = false; 42 description = "Sink for option definitions."; 43 type = mkOptionType { 44 name = "sink"; 45 check = x: true; 46 merge = loc: defs: false; 47 }; 48 apply = x: throw "Option value is not readable because the option is not declared."; 49 } // attrs); 50 51 mergeDefaultOption = loc: defs: 52 let list = getValues defs; in 53 if length list == 1 then head list 54 else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list) 55 else if all isList list then concatLists list 56 else if all isAttrs list then foldl' lib.mergeAttrs {} list 57 else if all isBool list then foldl' lib.or false list 58 else if all isString list then lib.concatStrings list 59 else if all isInt list && all (x: x == head list) list then head list 60 else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}."; 61 62 mergeOneOption = loc: defs: 63 if defs == [] then abort "This case should never happen." 64 else if length defs != 1 then 65 throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}." 66 else (head defs).value; 67 68 /* "Merge" option definitions by checking that they all have the same value. */ 69 mergeEqualOption = loc: defs: 70 if defs == [] then abort "This case should never happen." 71 else foldl' (val: def: 72 if def.value != val then 73 throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." 74 else 75 val) (head defs).value defs; 76 77 getValues = map (x: x.value); 78 getFiles = map (x: x.file); 79 80 # Generate documentation template from the list of option declaration like 81 # the set generated with filterOptionSets. 82 optionAttrSetToDocList = optionAttrSetToDocList' []; 83 84 optionAttrSetToDocList' = prefix: options: 85 concatMap (opt: 86 let 87 docOption = rec { 88 loc = opt.loc; 89 name = showOption opt.loc; 90 description = opt.description or (throw "Option `${name}' has no description."); 91 declarations = filter (x: x != unknownModule) opt.declarations; 92 internal = opt.internal or false; 93 visible = opt.visible or true; 94 readOnly = opt.readOnly or false; 95 type = opt.type.description or null; 96 } 97 // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; } 98 // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; } 99 // optionalAttrs (opt ? defaultText) { default = opt.defaultText; } 100 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; 101 102 subOptions = 103 let ss = opt.type.getSubOptions opt.loc; 104 in if ss != {} then optionAttrSetToDocList' opt.loc ss else []; 105 in 106 [ docOption ] ++ subOptions) (collect isOption options); 107 108 109 /* This function recursively removes all derivation attributes from 110 `x' except for the `name' attribute. This is to make the 111 generation of `options.xml' much more efficient: the XML 112 representation of derivations is very large (on the order of 113 megabytes) and is not actually used by the manual generator. */ 114 scrubOptionValue = x: 115 if isDerivation x then 116 { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; } 117 else if isList x then map scrubOptionValue x 118 else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"]) 119 else x; 120 121 122 /* For use in the example option attribute. It causes the given 123 text to be included verbatim in documentation. This is necessary 124 for example values that are not simple values, e.g., 125 functions. */ 126 literalExample = text: { _type = "literalExample"; inherit text; }; 127 128 129 /* Helper functions. */ 130 showOption = concatStringsSep "."; 131 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); 132 unknownModule = "<unknown-file>"; 133 134}