1with import ./lists.nix;
2with import ./trivial.nix;
3with import ./attrsets.nix;
4with import ./options.nix;
5with import ./debug.nix;
6with import ./types.nix;
7
8rec {
9
10 /* Evaluate a set of modules. The result is a set of two
11 attributes: ‘options’: the nested set of all option declarations,
12 and ‘config’: the nested set of all option values.
13 !!! Please think twice before adding to this argument list! The more
14 that is specified here instead of in the modules themselves the harder
15 it is to transparently move a set of modules to be a submodule of another
16 config (as the proper arguments need to be replicated at each call to
17 evalModules) and the less declarative the module set is. */
18 evalModules = { modules
19 , prefix ? []
20 , # This should only be used for special arguments that need to be evaluated
21 # when resolving module structure (like in imports). For everything else,
22 # there's _module.args.
23 specialArgs ? {}
24 , # This would be remove in the future, Prefer _module.args option instead.
25 args ? {}
26 , # This would be remove in the future, Prefer _module.check option instead.
27 check ? true
28 }:
29 let
30 # This internal module declare internal options under the `_module'
31 # attribute. These options are fragile, as they are used by the
32 # module system to change the interpretation of modules.
33 internalModule = rec {
34 _file = ./modules.nix;
35
36 key = _file;
37
38 options = {
39 _module.args = mkOption {
40 type = types.attrsOf types.unspecified;
41 internal = true;
42 description = "Arguments passed to each module.";
43 };
44
45 _module.check = mkOption {
46 type = types.bool;
47 internal = true;
48 default = check;
49 description = "Whether to check whether all option definitions have matching declarations.";
50 };
51 };
52
53 config = {
54 _module.args = args;
55 };
56 };
57
58 closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs);
59
60 # Note: the list of modules is reversed to maintain backward
61 # compatibility with the old module system. Not sure if this is
62 # the most sensible policy.
63 options = mergeModules prefix (reverseList closed);
64
65 # Traverse options and extract the option values into the final
66 # config set. At the same time, check whether all option
67 # definitions have matching declarations.
68 # !!! _module.check's value can't depend on any other config values
69 # without an infinite recursion. One way around this is to make the
70 # 'config' passed around to the modules be unconditionally unchecked,
71 # and only do the check in 'result'.
72 config = yieldConfig prefix options;
73 yieldConfig = prefix: set:
74 let res = removeAttrs (mapAttrs (n: v:
75 if isOption v then v.value
76 else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"];
77 in
78 if options._module.check.value && set ? _definedNames then
79 foldl' (res: m:
80 foldl' (res: name:
81 if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.")
82 res m.names)
83 res set._definedNames
84 else
85 res;
86 result = { inherit options config; };
87 in result;
88
89 /* Close a set of modules under the ‘imports’ relation. */
90 closeModules = modules: args:
91 let
92 toClosureList = file: parentKey: imap (n: x:
93 if isAttrs x || isFunction x then
94 let key = "${parentKey}:anon-${toString n}"; in
95 unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args)
96 else
97 let file = toString x; key = toString x; in
98 unifyModuleSyntax file key (applyIfFunction key (import x) args));
99 in
100 builtins.genericClosure {
101 startSet = toClosureList unknownModule "" modules;
102 operator = m: toClosureList m.file m.key m.imports;
103 };
104
105 /* Massage a module into canonical form, that is, a set consisting
106 of ‘options’, ‘config’ and ‘imports’ attributes. */
107 unifyModuleSyntax = file: key: m:
108 if m ? config || m ? options then
109 let badAttrs = removeAttrs m ["imports" "options" "config" "key" "_file"]; in
110 if badAttrs != {} then
111 throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
112 else
113 { file = m._file or file;
114 key = toString m.key or key;
115 imports = m.imports or [];
116 options = m.options or {};
117 config = m.config or {};
118 }
119 else
120 { file = m._file or file;
121 key = toString m.key or key;
122 imports = m.require or [] ++ m.imports or [];
123 options = {};
124 config = removeAttrs m ["key" "_file" "require" "imports"];
125 };
126
127 applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
128 let
129 # Module arguments are resolved in a strict manner when attribute set
130 # deconstruction is used. As the arguments are now defined with the
131 # config._module.args option, the strictness used on the attribute
132 # set argument would cause an infinite loop, if the result of the
133 # option is given as argument.
134 #
135 # To work-around the strictness issue on the deconstruction of the
136 # attributes set argument, we create a new attribute set which is
137 # constructed to satisfy the expected set of attributes. Thus calling
138 # a module will resolve strictly the attributes used as argument but
139 # not their values. The values are forwarding the result of the
140 # evaluation of the option.
141 requiredArgs = builtins.attrNames (builtins.functionArgs f);
142 context = name: ''while evaluating the module argument `${name}' in "${key}":'';
143 extraArgs = builtins.listToAttrs (map (name: {
144 inherit name;
145 value = addErrorContext (context name)
146 (args.${name} or config._module.args.${name});
147 }) requiredArgs);
148
149 # Note: we append in the opposite order such that we can add an error
150 # context on the explicited arguments of "args" too. This update
151 # operator is used to make the "args@{ ... }: with args.lib;" notation
152 # works.
153 in f (args // extraArgs)
154 else
155 f;
156
157 /* We have to pack and unpack submodules. We cannot wrap the expected
158 result of the function as we would no longer be able to list the arguments
159 of the submodule. (see applyIfFunction) */
160 unpackSubmodule = unpack: m: args:
161 if isType "submodule" m then
162 { _file = m.file; } // (unpack m.submodule args)
163 else unpack m args;
164
165 packSubmodule = file: m:
166 { _type = "submodule"; file = file; submodule = m; };
167
168 /* Merge a list of modules. This will recurse over the option
169 declarations in all modules, combining them into a single set.
170 At the same time, for each option declaration, it will merge the
171 corresponding option definitions in all machines, returning them
172 in the ‘value’ attribute of each option. */
173 mergeModules = prefix: modules:
174 mergeModules' prefix modules
175 (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
176
177 mergeModules' = prefix: options: configs:
178 listToAttrs (map (name: {
179 # We're descending into attribute ‘name’.
180 inherit name;
181 value =
182 let
183 loc = prefix ++ [name];
184 # Get all submodules that declare ‘name’.
185 decls = concatMap (m:
186 if m.options ? ${name}
187 then [ { inherit (m) file; options = m.options.${name}; } ]
188 else []
189 ) options;
190 # Get all submodules that define ‘name’.
191 defns = concatMap (m:
192 if m.config ? ${name}
193 then map (config: { inherit (m) file; inherit config; })
194 (pushDownProperties m.config.${name})
195 else []
196 ) configs;
197 nrOptions = count (m: isOption m.options) decls;
198 # Extract the definitions for this loc
199 defns' = map (m: { inherit (m) file; value = m.config.${name}; })
200 (filter (m: m.config ? ${name}) configs);
201 in
202 if nrOptions == length decls then
203 let opt = fixupOptionType loc (mergeOptionDecls loc decls);
204 in evalOptionValue loc opt defns'
205 else if nrOptions != 0 then
206 let
207 firstOption = findFirst (m: isOption m.options) "" decls;
208 firstNonOption = findFirst (m: !isOption m.options) "" decls;
209 in
210 throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
211 else
212 mergeModules' loc decls defns;
213 }) (concatMap (m: attrNames m.options) options))
214 // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
215
216 /* Merge multiple option declarations into a single declaration. In
217 general, there should be only one declaration of each option.
218 The exception is the ‘options’ attribute, which specifies
219 sub-options. These can be specified multiple times to allow one
220 module to add sub-options to an option declared somewhere else
221 (e.g. multiple modules define sub-options for ‘fileSystems’).
222
223 'loc' is the list of attribute names where the option is located.
224
225 'opts' is a list of modules. Each module has an options attribute which
226 correspond to the definition of 'loc' in 'opt.file'. */
227 mergeOptionDecls = loc: opts:
228 foldl' (res: opt:
229 if opt.options ? default && res ? default ||
230 opt.options ? example && res ? example ||
231 opt.options ? description && res ? description ||
232 opt.options ? apply && res ? apply ||
233 # Accept to merge options which have identical types.
234 opt.options ? type && res ? type && opt.options.type.name != res.type.name
235 then
236 throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
237 else
238 let
239 /* Add the modules of the current option to the list of modules
240 already collected. The options attribute except either a list of
241 submodules or a submodule. For each submodule, we add the file of the
242 current option declaration as the file use for the submodule. If the
243 submodule defines any filename, then we ignore the enclosing option file. */
244 options' = toList opt.options.options;
245 coerceOption = file: opt:
246 if isFunction opt then packSubmodule file opt
247 else packSubmodule file { options = opt; };
248 getSubModules = opt.options.type.getSubModules or null;
249 submodules =
250 if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options
251 else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options
252 else res.options;
253 in opt.options // res //
254 { declarations = res.declarations ++ [opt.file];
255 options = submodules;
256 }
257 ) { inherit loc; declarations = []; options = []; } opts;
258
259 /* Merge all the definitions of an option to produce the final
260 config value. */
261 evalOptionValue = loc: opt: defs:
262 let
263 # Add in the default value for this option, if any.
264 defs' =
265 (optional (opt ? default)
266 { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
267
268 # Handle properties, check types, and merge everything together.
269 res =
270 if opt.readOnly or false && length defs' > 1 then
271 throw "The option `${showOption loc}' is read-only, but it's set multiple times."
272 else
273 mergeDefinitions loc opt.type defs';
274
275 # Check whether the option is defined, and apply the ‘apply’
276 # function to the merged value. This allows options to yield a
277 # value computed from the definitions.
278 value =
279 if !res.isDefined then
280 throw "The option `${showOption loc}' is used but not defined."
281 else if opt ? apply then
282 opt.apply res.mergedValue
283 else
284 res.mergedValue;
285
286 in opt //
287 { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
288 definitions = map (def: def.value) res.defsFinal;
289 files = map (def: def.file) res.defsFinal;
290 inherit (res) isDefined;
291 };
292
293 # Merge definitions of a value of a given type.
294 mergeDefinitions = loc: type: defs: rec {
295 defsFinal =
296 let
297 # Process mkMerge and mkIf properties.
298 defs' = concatMap (m:
299 map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value)
300 ) defs;
301
302 # Process mkOverride properties.
303 defs'' = filterOverrides defs';
304
305 # Sort mkOrder properties.
306 defs''' =
307 # Avoid sorting if we don't have to.
308 if any (def: def.value._type or "" == "order") defs''
309 then sortProperties defs''
310 else defs'';
311 in defs''';
312
313 # Type-check the remaining definitions, and merge them.
314 mergedValue = foldl' (res: def:
315 if type.check def.value then res
316 else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.")
317 (type.merge loc defsFinal) defsFinal;
318
319 isDefined = defsFinal != [];
320
321 optionalValue =
322 if isDefined then { value = mergedValue; }
323 else {};
324 };
325
326 /* Given a config set, expand mkMerge properties, and push down the
327 other properties into the children. The result is a list of
328 config sets that do not have properties at top-level. For
329 example,
330
331 mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
332
333 is transformed into
334
335 [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
336
337 This transform is the critical step that allows mkIf conditions
338 to refer to the full configuration without creating an infinite
339 recursion.
340 */
341 pushDownProperties = cfg:
342 if cfg._type or "" == "merge" then
343 concatMap pushDownProperties cfg.contents
344 else if cfg._type or "" == "if" then
345 map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
346 else if cfg._type or "" == "override" then
347 map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
348 else # FIXME: handle mkOrder?
349 [ cfg ];
350
351 /* Given a config value, expand mkMerge properties, and discharge
352 any mkIf conditions. That is, this is the place where mkIf
353 conditions are actually evaluated. The result is a list of
354 config values. For example, ‘mkIf false x’ yields ‘[]’,
355 ‘mkIf true x’ yields ‘[x]’, and
356
357 mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
358
359 yields ‘[ 1 2 ]’.
360 */
361 dischargeProperties = def:
362 if def._type or "" == "merge" then
363 concatMap dischargeProperties def.contents
364 else if def._type or "" == "if" then
365 if def.condition then
366 dischargeProperties def.content
367 else
368 [ ]
369 else
370 [ def ];
371
372 /* Given a list of config values, process the mkOverride properties,
373 that is, return the values that have the highest (that is,
374 numerically lowest) priority, and strip the mkOverride
375 properties. For example,
376
377 [ { file = "/1"; value = mkOverride 10 "a"; }
378 { file = "/2"; value = mkOverride 20 "b"; }
379 { file = "/3"; value = "z"; }
380 { file = "/4"; value = mkOverride 10 "d"; }
381 ]
382
383 yields
384
385 [ { file = "/1"; value = "a"; }
386 { file = "/4"; value = "d"; }
387 ]
388
389 Note that "z" has the default priority 100.
390 */
391 filterOverrides = defs:
392 let
393 defaultPrio = 100;
394 getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio;
395 highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
396 strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
397 in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
398
399 /* Sort a list of properties. The sort priority of a property is
400 1000 by default, but can be overriden by wrapping the property
401 using mkOrder. */
402 sortProperties = defs:
403 let
404 strip = def:
405 if def.value._type or "" == "order"
406 then def // { value = def.value.content; inherit (def.value) priority; }
407 else def;
408 defs' = map strip defs;
409 compare = a: b: (a.priority or 1000) < (b.priority or 1000);
410 in sort compare defs';
411
412 /* Hack for backward compatibility: convert options of type
413 optionSet to options of type submodule. FIXME: remove
414 eventually. */
415 fixupOptionType = loc: opt:
416 let
417 options = opt.options or
418 (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
419 f = tp:
420 if tp.name == "option set" || tp.name == "submodule" then
421 throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
422 else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options)
423 else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options)
424 else if tp.name == "list of option sets" then types.listOf (types.submodule options)
425 else if tp.name == "null or option set" then types.nullOr (types.submodule options)
426 else tp;
427 in
428 if opt.type.getSubModules or null == null
429 then opt // { type = f (opt.type or types.unspecified); }
430 else opt // { type = opt.type.substSubModules opt.options; options = []; };
431
432
433 /* Properties. */
434
435 mkIf = condition: content:
436 { _type = "if";
437 inherit condition content;
438 };
439
440 mkAssert = assertion: message: content:
441 mkIf
442 (if assertion then true else throw "\nFailed assertion: ${message}")
443 content;
444
445 mkMerge = contents:
446 { _type = "merge";
447 inherit contents;
448 };
449
450 mkOverride = priority: content:
451 { _type = "override";
452 inherit priority content;
453 };
454
455 mkOptionDefault = mkOverride 1001; # priority of option defaults
456 mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
457 mkForce = mkOverride 50;
458 mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
459
460 mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0);
461
462 mkFixStrictness = id; # obsolete, no-op
463
464 mkOrder = priority: content:
465 { _type = "order";
466 inherit priority content;
467 };
468
469 mkBefore = mkOrder 500;
470 mkAfter = mkOrder 1500;
471
472 # Convenient property used to transfer all definitions and their
473 # properties from one option to another. This property is useful for
474 # renaming options, and also for including properties from another module
475 # system, including sub-modules.
476 #
477 # { config, options, ... }:
478 #
479 # {
480 # # 'bar' might not always be defined in the current module-set.
481 # config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
482 #
483 # # 'barbaz' has to be defined in the current module-set.
484 # config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
485 # }
486 #
487 # Note, this is different than taking the value of the option and using it
488 # as a definition, as the new definition will not keep the mkOverride /
489 # mkDefault properties of the previous option.
490 #
491 mkAliasDefinitions = mkAliasAndWrapDefinitions id;
492 mkAliasAndWrapDefinitions = wrap: option:
493 mkMerge
494 (optional (isOption option && option.isDefined)
495 (wrap (mkMerge option.definitions)));
496
497
498 /* Compatibility. */
499 fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
500
501}