Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

455 Zeilen
16 KiB

  1. # Definitions related to run-time type checking. Used in particular
  2. # to type-check NixOS configurations.
  3. { lib }:
  4. with lib.lists;
  5. with lib.attrsets;
  6. with lib.options;
  7. with lib.trivial;
  8. with lib.strings;
  9. let
  10. inherit (lib.modules) mergeDefinitions filterOverrides;
  11. outer_types =
  12. rec {
  13. isType = type: x: (x._type or "") == type;
  14. setType = typeName: value: value // {
  15. _type = typeName;
  16. };
  17. # Default type merging function
  18. # takes two type functors and return the merged type
  19. defaultTypeMerge = f: f':
  20. let wrapped = f.wrapped.typeMerge f'.wrapped.functor;
  21. payload = f.binOp f.payload f'.payload;
  22. in
  23. # cannot merge different types
  24. if f.name != f'.name
  25. then null
  26. # simple types
  27. else if (f.wrapped == null && f'.wrapped == null)
  28. && (f.payload == null && f'.payload == null)
  29. then f.type
  30. # composed types
  31. else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
  32. then f.type wrapped
  33. # value types
  34. else if (f.payload != null && f'.payload != null) && (payload != null)
  35. then f.type payload
  36. else null;
  37. # Default type functor
  38. defaultFunctor = name: {
  39. inherit name;
  40. type = types."${name}" or null;
  41. wrapped = null;
  42. payload = null;
  43. binOp = a: b: null;
  44. };
  45. isOptionType = isType "option-type";
  46. mkOptionType =
  47. { # Human-readable representation of the type, should be equivalent to
  48. # the type function name.
  49. name
  50. , # Description of the type, defined recursively by embedding the wrapped type if any.
  51. description ? null
  52. , # Function applied to each definition that should return true if
  53. # its type-correct, false otherwise.
  54. check ? (x: true)
  55. , # Merge a list of definitions together into a single value.
  56. # This function is called with two arguments: the location of
  57. # the option in the configuration as a list of strings
  58. # (e.g. ["boot" "loader "grub" "enable"]), and a list of
  59. # definition values and locations (e.g. [ { file = "/foo.nix";
  60. # value = 1; } { file = "/bar.nix"; value = 2 } ]).
  61. merge ? mergeDefaultOption
  62. , # Return a flat list of sub-options. Used to generate
  63. # documentation.
  64. getSubOptions ? prefix: {}
  65. , # List of modules if any, or null if none.
  66. getSubModules ? null
  67. , # Function for building the same option type with a different list of
  68. # modules.
  69. substSubModules ? m: null
  70. , # Function that merge type declarations.
  71. # internal, takes a functor as argument and returns the merged type.
  72. # returning null means the type is not mergeable
  73. typeMerge ? defaultTypeMerge functor
  74. , # The type functor.
  75. # internal, representation of the type as an attribute set.
  76. # name: name of the type
  77. # type: type function.
  78. # wrapped: the type wrapped in case of compound types.
  79. # payload: values of the type, two payloads of the same type must be
  80. # combinable with the binOp binary operation.
  81. # binOp: binary operation that merge two payloads of the same type.
  82. functor ? defaultFunctor name
  83. }:
  84. { _type = "option-type";
  85. inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor;
  86. description = if description == null then name else description;
  87. };
  88. # When adding new types don't forget to document them in
  89. # nixos/doc/manual/development/option-types.xml!
  90. types = rec {
  91. unspecified = mkOptionType {
  92. name = "unspecified";
  93. };
  94. bool = mkOptionType {
  95. name = "bool";
  96. description = "boolean";
  97. check = isBool;
  98. merge = mergeEqualOption;
  99. };
  100. int = mkOptionType rec {
  101. name = "int";
  102. description = "signed integer";
  103. check = isInt;
  104. merge = mergeOneOption;
  105. };
  106. # Specialized subdomains of int
  107. ints =
  108. let
  109. betweenDesc = lowest: highest:
  110. "${toString lowest} and ${toString highest} (both inclusive)";
  111. between = lowest: highest: assert lowest <= highest;
  112. addCheck int (x: x >= lowest && x <= highest) // {
  113. name = "intBetween";
  114. description = "integer between ${betweenDesc lowest highest}";
  115. };
  116. ign = lowest: highest: name: docStart:
  117. between lowest highest // {
  118. inherit name;
  119. description = docStart + "; between ${betweenDesc lowest highest}";
  120. };
  121. unsign = bit: range: ign 0 (range - 1)
  122. "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
  123. sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1)
  124. "signedInt${toString bit}" "${toString bit} bit signed integer";
  125. in rec {
  126. /* An int with a fixed range.
  127. *
  128. * Example:
  129. * (ints.between 0 100).check (-1)
  130. * => false
  131. * (ints.between 0 100).check (101)
  132. * => false
  133. * (ints.between 0 0).check 0
  134. * => true
  135. */
  136. inherit between;
  137. unsigned = addCheck types.int (x: x >= 0) // {
  138. name = "unsignedInt";
  139. description = "unsigned integer, meaning >=0";
  140. };
  141. positive = addCheck types.int (x: x > 0) // {
  142. name = "positiveInt";
  143. description = "positive integer, meaning >0";
  144. };
  145. u8 = unsign 8 256;
  146. u16 = unsign 16 65536;
  147. # the biggest int a 64-bit Nix accepts is 2^63 - 1 (9223372036854775808), for a 32-bit Nix it is 2^31 - 1 (2147483647)
  148. # the smallest int a 64-bit Nix accepts is -2^63 (-9223372036854775807), for a 32-bit Nix it is -2^31 (-2147483648)
  149. # u32 = unsign 32 4294967296;
  150. # u64 = unsign 64 18446744073709551616;
  151. s8 = sign 8 256;
  152. s16 = sign 16 65536;
  153. # s32 = sign 32 4294967296;
  154. };
  155. str = mkOptionType {
  156. name = "str";
  157. description = "string";
  158. check = isString;
  159. merge = mergeOneOption;
  160. };
  161. strMatching = pattern: mkOptionType {
  162. name = "strMatching ${escapeNixString pattern}";
  163. description = "string matching the pattern ${pattern}";
  164. check = x: str.check x && builtins.match pattern x != null;
  165. inherit (str) merge;
  166. };
  167. # Merge multiple definitions by concatenating them (with the given
  168. # separator between the values).
  169. separatedString = sep: mkOptionType rec {
  170. name = "separatedString";
  171. description = "string";
  172. check = isString;
  173. merge = loc: defs: concatStringsSep sep (getValues defs);
  174. functor = (defaultFunctor name) // {
  175. payload = sep;
  176. binOp = sepLhs: sepRhs:
  177. if sepLhs == sepRhs then sepLhs
  178. else null;
  179. };
  180. };
  181. lines = separatedString "\n";
  182. commas = separatedString ",";
  183. envVar = separatedString ":";
  184. # Deprecated; should not be used because it quietly concatenates
  185. # strings, which is usually not what you want.
  186. string = separatedString "";
  187. attrs = mkOptionType {
  188. name = "attrs";
  189. description = "attribute set";
  190. check = isAttrs;
  191. merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
  192. };
  193. # derivation is a reserved keyword.
  194. package = mkOptionType {
  195. name = "package";
  196. check = x: isDerivation x || isStorePath x;
  197. merge = loc: defs:
  198. let res = mergeOneOption loc defs;
  199. in if isDerivation res then res else toDerivation res;
  200. };
  201. shellPackage = package // {
  202. check = x: (package.check x) && (hasAttr "shellPath" x);
  203. };
  204. path = mkOptionType {
  205. name = "path";
  206. # Hacky: there is no ‘isPath’ primop.
  207. check = x: builtins.substring 0 1 (toString x) == "/";
  208. merge = mergeOneOption;
  209. };
  210. # drop this in the future:
  211. list = builtins.trace "`types.list` is deprecated; use `types.listOf` instead" types.listOf;
  212. listOf = elemType: mkOptionType rec {
  213. name = "listOf";
  214. description = "list of ${elemType.description}s";
  215. check = isList;
  216. merge = loc: defs:
  217. map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def:
  218. if isList def.value then
  219. imap1 (m: def':
  220. (mergeDefinitions
  221. (loc ++ ["[definition ${toString n}-entry ${toString m}]"])
  222. elemType
  223. [{ inherit (def) file; value = def'; }]
  224. ).optionalValue
  225. ) def.value
  226. else
  227. throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs)));
  228. getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
  229. getSubModules = elemType.getSubModules;
  230. substSubModules = m: listOf (elemType.substSubModules m);
  231. functor = (defaultFunctor name) // { wrapped = elemType; };
  232. };
  233. nonEmptyListOf = elemType:
  234. let list = addCheck (types.listOf elemType) (l: l != []);
  235. in list // { description = "non-empty " + list.description; };
  236. attrsOf = elemType: mkOptionType rec {
  237. name = "attrsOf";
  238. description = "attribute set of ${elemType.description}s";
  239. check = isAttrs;
  240. merge = loc: defs:
  241. mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
  242. (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
  243. )
  244. # Push down position info.
  245. (map (def: listToAttrs (mapAttrsToList (n: def':
  246. { name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs)));
  247. getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
  248. getSubModules = elemType.getSubModules;
  249. substSubModules = m: attrsOf (elemType.substSubModules m);
  250. functor = (defaultFunctor name) // { wrapped = elemType; };
  251. };
  252. # List or attribute set of ...
  253. loaOf = elemType:
  254. let
  255. convertIfList = defIdx: def:
  256. if isList def.value then
  257. { inherit (def) file;
  258. value = listToAttrs (
  259. imap1 (elemIdx: elem:
  260. { name = elem.name or "unnamed-${toString defIdx}.${toString elemIdx}";
  261. value = elem;
  262. }) def.value);
  263. }
  264. else
  265. def;
  266. listOnly = listOf elemType;
  267. attrOnly = attrsOf elemType;
  268. in mkOptionType rec {
  269. name = "loaOf";
  270. description = "list or attribute set of ${elemType.description}s";
  271. check = x: isList x || isAttrs x;
  272. merge = loc: defs: attrOnly.merge loc (imap1 convertIfList defs);
  273. getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
  274. getSubModules = elemType.getSubModules;
  275. substSubModules = m: loaOf (elemType.substSubModules m);
  276. functor = (defaultFunctor name) // { wrapped = elemType; };
  277. };
  278. # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
  279. uniq = elemType: mkOptionType rec {
  280. name = "uniq";
  281. inherit (elemType) description check;
  282. merge = mergeOneOption;
  283. getSubOptions = elemType.getSubOptions;
  284. getSubModules = elemType.getSubModules;
  285. substSubModules = m: uniq (elemType.substSubModules m);
  286. functor = (defaultFunctor name) // { wrapped = elemType; };
  287. };
  288. # Null or value of ...
  289. nullOr = elemType: mkOptionType rec {
  290. name = "nullOr";
  291. description = "null or ${elemType.description}";
  292. check = x: x == null || elemType.check x;
  293. merge = loc: defs:
  294. let nrNulls = count (def: def.value == null) defs; in
  295. if nrNulls == length defs then null
  296. else if nrNulls != 0 then
  297. throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
  298. else elemType.merge loc defs;
  299. getSubOptions = elemType.getSubOptions;
  300. getSubModules = elemType.getSubModules;
  301. substSubModules = m: nullOr (elemType.substSubModules m);
  302. functor = (defaultFunctor name) // { wrapped = elemType; };
  303. };
  304. # A submodule (like typed attribute set). See NixOS manual.
  305. submodule = opts:
  306. let
  307. opts' = toList opts;
  308. inherit (lib.modules) evalModules;
  309. in
  310. mkOptionType rec {
  311. name = "submodule";
  312. check = x: isAttrs x || isFunction x;
  313. merge = loc: defs:
  314. let
  315. coerce = def: if isFunction def then def else { config = def; };
  316. modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
  317. in (evalModules {
  318. inherit modules;
  319. args.name = last loc;
  320. prefix = loc;
  321. }).config;
  322. getSubOptions = prefix: (evalModules
  323. { modules = opts'; inherit prefix;
  324. # This is a work-around due to the fact that some sub-modules,
  325. # such as the one included in an attribute set, expects a "args"
  326. # attribute to be given to the sub-module. As the option
  327. # evaluation does not have any specific attribute name, we
  328. # provide a default one for the documentation.
  329. #
  330. # This is mandatory as some option declaration might use the
  331. # "name" attribute given as argument of the submodule and use it
  332. # as the default of option declarations.
  333. args.name = "&lt;name&gt;";
  334. }).options;
  335. getSubModules = opts';
  336. substSubModules = m: submodule m;
  337. functor = (defaultFunctor name) // {
  338. # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
  339. # each submodule with its location.
  340. payload = [];
  341. binOp = lhs: rhs: [];
  342. };
  343. };
  344. # A value from a set of allowed ones.
  345. enum = values:
  346. let
  347. show = v:
  348. if builtins.isString v then ''"${v}"''
  349. else if builtins.isInt v then builtins.toString v
  350. else ''<${builtins.typeOf v}>'';
  351. in
  352. mkOptionType rec {
  353. name = "enum";
  354. description = "one of ${concatMapStringsSep ", " show values}";
  355. check = flip elem values;
  356. merge = mergeOneOption;
  357. functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
  358. };
  359. # Either value of type `t1` or `t2`.
  360. either = t1: t2: mkOptionType rec {
  361. name = "either";
  362. description = "${t1.description} or ${t2.description}";
  363. check = x: t1.check x || t2.check x;
  364. merge = loc: defs:
  365. let
  366. defList = map (d: d.value) defs;
  367. in
  368. if all (x: t1.check x) defList
  369. then t1.merge loc defs
  370. else if all (x: t2.check x) defList
  371. then t2.merge loc defs
  372. else mergeOneOption loc defs;
  373. typeMerge = f':
  374. let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
  375. mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
  376. in
  377. if (name == f'.name) && (mt1 != null) && (mt2 != null)
  378. then functor.type mt1 mt2
  379. else null;
  380. functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
  381. };
  382. # Either value of type `finalType` or `coercedType`, the latter is
  383. # converted to `finalType` using `coerceFunc`.
  384. coercedTo = coercedType: coerceFunc: finalType:
  385. assert coercedType.getSubModules == null;
  386. mkOptionType rec {
  387. name = "coercedTo";
  388. description = "${finalType.description} or ${coercedType.description}";
  389. check = x: finalType.check x || coercedType.check x;
  390. merge = loc: defs:
  391. let
  392. coerceVal = val:
  393. if finalType.check val then val
  394. else let
  395. coerced = coerceFunc val;
  396. in assert finalType.check coerced; coerced;
  397. in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
  398. getSubOptions = finalType.getSubOptions;
  399. getSubModules = finalType.getSubModules;
  400. substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
  401. typeMerge = t1: t2: null;
  402. functor = (defaultFunctor name) // { wrapped = finalType; };
  403. };
  404. # Obsolete alternative to configOf. It takes its option
  405. # declarations from the ‘options’ attribute of containing option
  406. # declaration.
  407. optionSet = mkOptionType {
  408. name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "optionSet";
  409. description = "option set";
  410. };
  411. # Augment the given type with an additional type check function.
  412. addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
  413. };
  414. };
  415. in outer_types // outer_types.types