2017-07-29 03:05:35 +03:00
|
|
|
|
{ lib }:
|
|
|
|
|
|
|
|
|
|
with lib.lists;
|
|
|
|
|
with lib.strings;
|
|
|
|
|
with lib.trivial;
|
|
|
|
|
with lib.attrsets;
|
|
|
|
|
with lib.options;
|
|
|
|
|
with lib.debug;
|
|
|
|
|
with lib.types;
|
2009-07-13 20:18:52 +04:00
|
|
|
|
|
|
|
|
|
rec {
|
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
/* Evaluate a set of modules. The result is a set of two
|
|
|
|
|
attributes: ‘options’: the nested set of all option declarations,
|
2014-05-06 00:30:51 +04:00
|
|
|
|
and ‘config’: the nested set of all option values.
|
|
|
|
|
!!! Please think twice before adding to this argument list! The more
|
|
|
|
|
that is specified here instead of in the modules themselves the harder
|
|
|
|
|
it is to transparently move a set of modules to be a submodule of another
|
|
|
|
|
config (as the proper arguments need to be replicated at each call to
|
|
|
|
|
evalModules) and the less declarative the module set is. */
|
|
|
|
|
evalModules = { modules
|
|
|
|
|
, prefix ? []
|
2015-06-07 01:38:08 +03:00
|
|
|
|
, # This should only be used for special arguments that need to be evaluated
|
|
|
|
|
# when resolving module structure (like in imports). For everything else,
|
2017-02-15 01:18:44 +03:00
|
|
|
|
# there's _module.args. If specialArgs.modulesPath is defined it will be
|
|
|
|
|
# used as the base path for disabledModules.
|
2015-06-07 01:38:08 +03:00
|
|
|
|
specialArgs ? {}
|
2015-03-13 01:19:23 +03:00
|
|
|
|
, # This would be remove in the future, Prefer _module.args option instead.
|
2014-05-06 00:30:51 +04:00
|
|
|
|
args ? {}
|
2016-03-01 22:47:08 +03:00
|
|
|
|
, # This would be remove in the future, Prefer _module.check option instead.
|
|
|
|
|
check ? true
|
2014-05-06 00:30:51 +04:00
|
|
|
|
}:
|
2009-07-13 20:18:52 +04:00
|
|
|
|
let
|
2015-03-13 01:19:23 +03:00
|
|
|
|
# This internal module declare internal options under the `_module'
|
|
|
|
|
# attribute. These options are fragile, as they are used by the
|
|
|
|
|
# module system to change the interpretation of modules.
|
2014-05-05 23:52:33 +04:00
|
|
|
|
internalModule = rec {
|
2014-05-05 23:18:53 +04:00
|
|
|
|
_file = ./modules.nix;
|
|
|
|
|
|
2014-05-05 23:52:33 +04:00
|
|
|
|
key = _file;
|
2014-05-05 23:18:53 +04:00
|
|
|
|
|
|
|
|
|
options = {
|
2015-03-13 01:19:23 +03:00
|
|
|
|
_module.args = mkOption {
|
2014-05-05 23:18:53 +04:00
|
|
|
|
type = types.attrsOf types.unspecified;
|
|
|
|
|
internal = true;
|
2015-03-13 01:19:23 +03:00
|
|
|
|
description = "Arguments passed to each module.";
|
2014-05-05 23:18:53 +04:00
|
|
|
|
};
|
2014-05-06 00:23:57 +04:00
|
|
|
|
|
2015-03-13 01:19:23 +03:00
|
|
|
|
_module.check = mkOption {
|
2015-06-15 19:10:26 +03:00
|
|
|
|
type = types.bool;
|
2014-05-06 00:23:57 +04:00
|
|
|
|
internal = true;
|
2016-03-01 22:47:08 +03:00
|
|
|
|
default = check;
|
2015-03-13 01:19:23 +03:00
|
|
|
|
description = "Whether to check whether all option definitions have matching declarations.";
|
2014-05-06 00:23:57 +04:00
|
|
|
|
};
|
2014-05-05 23:18:53 +04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = {
|
2015-03-13 01:19:23 +03:00
|
|
|
|
_module.args = args;
|
2014-05-05 23:18:53 +04:00
|
|
|
|
};
|
|
|
|
|
};
|
2015-03-13 01:19:23 +03:00
|
|
|
|
|
2015-08-09 15:40:01 +03:00
|
|
|
|
closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs);
|
2015-03-13 01:19:23 +03:00
|
|
|
|
|
2017-02-15 01:18:44 +03:00
|
|
|
|
options = mergeModules prefix (reverseList (filterModules (specialArgs.modulesPath or "") closed));
|
2015-03-13 01:19:23 +03:00
|
|
|
|
|
2013-10-29 19:14:58 +04:00
|
|
|
|
# Traverse options and extract the option values into the final
|
|
|
|
|
# config set. At the same time, check whether all option
|
|
|
|
|
# definitions have matching declarations.
|
2015-03-13 01:19:23 +03:00
|
|
|
|
# !!! _module.check's value can't depend on any other config values
|
2014-05-08 00:43:18 +04:00
|
|
|
|
# without an infinite recursion. One way around this is to make the
|
|
|
|
|
# 'config' passed around to the modules be unconditionally unchecked,
|
|
|
|
|
# and only do the check in 'result'.
|
2013-10-30 17:21:41 +04:00
|
|
|
|
config = yieldConfig prefix options;
|
2013-10-29 19:14:58 +04:00
|
|
|
|
yieldConfig = prefix: set:
|
|
|
|
|
let res = removeAttrs (mapAttrs (n: v:
|
|
|
|
|
if isOption v then v.value
|
|
|
|
|
else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"];
|
|
|
|
|
in
|
2015-03-13 01:19:23 +03:00
|
|
|
|
if options._module.check.value && set ? _definedNames then
|
2015-07-23 18:19:21 +03:00
|
|
|
|
foldl' (res: m:
|
|
|
|
|
foldl' (res: name:
|
2014-10-05 02:03:52 +04:00
|
|
|
|
if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.")
|
2013-10-29 19:14:58 +04:00
|
|
|
|
res m.names)
|
|
|
|
|
res set._definedNames
|
|
|
|
|
else
|
|
|
|
|
res;
|
2013-10-28 03:56:22 +04:00
|
|
|
|
result = { inherit options config; };
|
|
|
|
|
in result;
|
|
|
|
|
|
2017-02-15 01:18:44 +03:00
|
|
|
|
|
|
|
|
|
# Filter disabled modules. Modules can be disabled allowing
|
|
|
|
|
# their implementation to be replaced.
|
|
|
|
|
filterModules = modulesPath: modules:
|
|
|
|
|
let
|
|
|
|
|
moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m;
|
|
|
|
|
disabledKeys = map moduleKey (concatMap (m: m.disabledModules) modules);
|
|
|
|
|
in
|
|
|
|
|
filter (m: !(elem m.key disabledKeys)) modules;
|
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
/* Close a set of modules under the ‘imports’ relation. */
|
|
|
|
|
closeModules = modules: args:
|
2013-06-07 11:42:46 +04:00
|
|
|
|
let
|
2017-07-05 01:29:23 +03:00
|
|
|
|
toClosureList = file: parentKey: imap1 (n: x:
|
2013-11-12 16:48:19 +04:00
|
|
|
|
if isAttrs x || isFunction x then
|
2015-05-13 23:44:04 +03:00
|
|
|
|
let key = "${parentKey}:anon-${toString n}"; in
|
|
|
|
|
unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args)
|
2013-10-28 03:56:22 +04:00
|
|
|
|
else
|
2015-05-13 23:44:04 +03:00
|
|
|
|
let file = toString x; key = toString x; in
|
|
|
|
|
unifyModuleSyntax file key (applyIfFunction key (import x) args));
|
2013-10-28 03:56:22 +04:00
|
|
|
|
in
|
|
|
|
|
builtins.genericClosure {
|
2013-10-28 20:24:14 +04:00
|
|
|
|
startSet = toClosureList unknownModule "" modules;
|
|
|
|
|
operator = m: toClosureList m.file m.key m.imports;
|
2013-06-07 11:42:46 +04:00
|
|
|
|
};
|
2009-10-09 22:11:24 +04:00
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
/* Massage a module into canonical form, that is, a set consisting
|
|
|
|
|
of ‘options’, ‘config’ and ‘imports’ attributes. */
|
2013-10-28 07:46:36 +04:00
|
|
|
|
unifyModuleSyntax = file: key: m:
|
2017-03-12 01:39:40 +03:00
|
|
|
|
let metaSet = if m ? meta
|
2016-05-09 08:53:27 +03:00
|
|
|
|
then { meta = m.meta; }
|
|
|
|
|
else {};
|
|
|
|
|
in
|
2013-10-28 20:40:36 +04:00
|
|
|
|
if m ? config || m ? options then
|
2017-02-15 01:18:44 +03:00
|
|
|
|
let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in
|
2013-10-28 03:56:22 +04:00
|
|
|
|
if badAttrs != {} then
|
2014-11-02 01:29:38 +03:00
|
|
|
|
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'."
|
2013-10-28 03:56:22 +04:00
|
|
|
|
else
|
2013-10-30 17:21:41 +04:00
|
|
|
|
{ file = m._file or file;
|
2013-10-29 17:15:33 +04:00
|
|
|
|
key = toString m.key or key;
|
2017-02-15 01:18:44 +03:00
|
|
|
|
disabledModules = m.disabledModules or [];
|
2013-10-28 03:56:22 +04:00
|
|
|
|
imports = m.imports or [];
|
|
|
|
|
options = m.options or {};
|
2016-05-09 08:53:27 +03:00
|
|
|
|
config = mkMerge [ (m.config or {}) metaSet ];
|
2013-10-28 03:56:22 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
2013-10-30 17:21:41 +04:00
|
|
|
|
{ file = m._file or file;
|
2013-10-29 17:15:33 +04:00
|
|
|
|
key = toString m.key or key;
|
2017-02-15 01:18:44 +03:00
|
|
|
|
disabledModules = m.disabledModules or [];
|
2013-10-28 20:40:36 +04:00
|
|
|
|
imports = m.require or [] ++ m.imports or [];
|
2013-10-28 03:56:22 +04:00
|
|
|
|
options = {};
|
2017-02-15 01:18:44 +03:00
|
|
|
|
config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ];
|
2013-10-28 03:56:22 +04:00
|
|
|
|
};
|
2009-10-09 22:11:24 +04:00
|
|
|
|
|
2015-05-13 23:44:04 +03:00
|
|
|
|
applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
|
2014-05-05 23:18:53 +04:00
|
|
|
|
let
|
2015-03-12 01:30:30 +03:00
|
|
|
|
# Module arguments are resolved in a strict manner when attribute set
|
|
|
|
|
# deconstruction is used. As the arguments are now defined with the
|
2015-03-13 01:19:23 +03:00
|
|
|
|
# config._module.args option, the strictness used on the attribute
|
2015-03-12 01:30:30 +03:00
|
|
|
|
# set argument would cause an infinite loop, if the result of the
|
|
|
|
|
# option is given as argument.
|
|
|
|
|
#
|
|
|
|
|
# To work-around the strictness issue on the deconstruction of the
|
|
|
|
|
# attributes set argument, we create a new attribute set which is
|
|
|
|
|
# constructed to satisfy the expected set of attributes. Thus calling
|
|
|
|
|
# a module will resolve strictly the attributes used as argument but
|
|
|
|
|
# not their values. The values are forwarding the result of the
|
|
|
|
|
# evaluation of the option.
|
2014-05-05 23:18:53 +04:00
|
|
|
|
requiredArgs = builtins.attrNames (builtins.functionArgs f);
|
2015-05-13 23:44:04 +03:00
|
|
|
|
context = name: ''while evaluating the module argument `${name}' in "${key}":'';
|
2014-05-05 23:18:53 +04:00
|
|
|
|
extraArgs = builtins.listToAttrs (map (name: {
|
|
|
|
|
inherit name;
|
2015-05-13 23:44:04 +03:00
|
|
|
|
value = addErrorContext (context name)
|
|
|
|
|
(args.${name} or config._module.args.${name});
|
2014-05-05 23:18:53 +04:00
|
|
|
|
}) requiredArgs);
|
2015-05-13 23:44:04 +03:00
|
|
|
|
|
|
|
|
|
# Note: we append in the opposite order such that we can add an error
|
|
|
|
|
# context on the explicited arguments of "args" too. This update
|
|
|
|
|
# operator is used to make the "args@{ ... }: with args.lib;" notation
|
|
|
|
|
# works.
|
|
|
|
|
in f (args // extraArgs)
|
2014-05-05 23:18:53 +04:00
|
|
|
|
else
|
|
|
|
|
f;
|
2013-10-28 03:56:22 +04:00
|
|
|
|
|
2015-03-12 01:30:30 +03:00
|
|
|
|
/* We have to pack and unpack submodules. We cannot wrap the expected
|
|
|
|
|
result of the function as we would no longer be able to list the arguments
|
|
|
|
|
of the submodule. (see applyIfFunction) */
|
|
|
|
|
unpackSubmodule = unpack: m: args:
|
|
|
|
|
if isType "submodule" m then
|
|
|
|
|
{ _file = m.file; } // (unpack m.submodule args)
|
|
|
|
|
else unpack m args;
|
|
|
|
|
|
|
|
|
|
packSubmodule = file: m:
|
|
|
|
|
{ _type = "submodule"; file = file; submodule = m; };
|
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
/* Merge a list of modules. This will recurse over the option
|
|
|
|
|
declarations in all modules, combining them into a single set.
|
|
|
|
|
At the same time, for each option declaration, it will merge the
|
|
|
|
|
corresponding option definitions in all machines, returning them
|
|
|
|
|
in the ‘value’ attribute of each option. */
|
2016-03-01 22:47:08 +03:00
|
|
|
|
mergeModules = prefix: modules:
|
|
|
|
|
mergeModules' prefix modules
|
2013-10-28 07:46:36 +04:00
|
|
|
|
(concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
|
2013-10-28 03:56:22 +04:00
|
|
|
|
|
2016-03-01 22:47:08 +03:00
|
|
|
|
mergeModules' = prefix: options: configs:
|
2013-10-29 19:14:58 +04:00
|
|
|
|
listToAttrs (map (name: {
|
2013-10-28 07:46:36 +04:00
|
|
|
|
# We're descending into attribute ‘name’.
|
|
|
|
|
inherit name;
|
|
|
|
|
value =
|
|
|
|
|
let
|
2013-10-28 17:25:58 +04:00
|
|
|
|
loc = prefix ++ [name];
|
2013-10-28 07:46:36 +04:00
|
|
|
|
# Get all submodules that declare ‘name’.
|
2015-07-23 19:31:17 +03:00
|
|
|
|
decls = concatMap (m:
|
2014-10-05 02:03:52 +04:00
|
|
|
|
if m.options ? ${name}
|
|
|
|
|
then [ { inherit (m) file; options = m.options.${name}; } ]
|
2013-10-28 10:51:46 +04:00
|
|
|
|
else []
|
2015-07-23 19:31:17 +03:00
|
|
|
|
) options;
|
2013-10-28 07:46:36 +04:00
|
|
|
|
# Get all submodules that define ‘name’.
|
2015-07-23 19:31:17 +03:00
|
|
|
|
defns = concatMap (m:
|
2014-10-05 02:03:52 +04:00
|
|
|
|
if m.config ? ${name}
|
2013-10-28 10:51:46 +04:00
|
|
|
|
then map (config: { inherit (m) file; inherit config; })
|
2014-10-05 02:03:52 +04:00
|
|
|
|
(pushDownProperties m.config.${name})
|
2013-10-28 10:51:46 +04:00
|
|
|
|
else []
|
2015-07-23 19:31:17 +03:00
|
|
|
|
) configs;
|
2013-10-28 07:46:36 +04:00
|
|
|
|
nrOptions = count (m: isOption m.options) decls;
|
2014-05-02 01:29:30 +04:00
|
|
|
|
# Extract the definitions for this loc
|
|
|
|
|
defns' = map (m: { inherit (m) file; value = m.config.${name}; })
|
|
|
|
|
(filter (m: m.config ? ${name}) configs);
|
2013-10-28 07:46:36 +04:00
|
|
|
|
in
|
|
|
|
|
if nrOptions == length decls then
|
2016-03-01 22:47:08 +03:00
|
|
|
|
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
|
|
|
|
in evalOptionValue loc opt defns'
|
2013-10-28 07:46:36 +04:00
|
|
|
|
else if nrOptions != 0 then
|
|
|
|
|
let
|
|
|
|
|
firstOption = findFirst (m: isOption m.options) "" decls;
|
|
|
|
|
firstNonOption = findFirst (m: !isOption m.options) "" decls;
|
|
|
|
|
in
|
2013-10-28 17:25:58 +04:00
|
|
|
|
throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
|
2013-10-28 07:46:36 +04:00
|
|
|
|
else
|
2016-03-01 22:47:08 +03:00
|
|
|
|
mergeModules' loc decls defns;
|
2013-10-29 19:14:58 +04:00
|
|
|
|
}) (concatMap (m: attrNames m.options) options))
|
|
|
|
|
// { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
|
2013-10-28 03:56:22 +04:00
|
|
|
|
|
|
|
|
|
/* Merge multiple option declarations into a single declaration. In
|
|
|
|
|
general, there should be only one declaration of each option.
|
|
|
|
|
The exception is the ‘options’ attribute, which specifies
|
|
|
|
|
sub-options. These can be specified multiple times to allow one
|
|
|
|
|
module to add sub-options to an option declared somewhere else
|
2014-08-29 16:38:19 +04:00
|
|
|
|
(e.g. multiple modules define sub-options for ‘fileSystems’).
|
|
|
|
|
|
|
|
|
|
'loc' is the list of attribute names where the option is located.
|
|
|
|
|
|
|
|
|
|
'opts' is a list of modules. Each module has an options attribute which
|
|
|
|
|
correspond to the definition of 'loc' in 'opt.file'. */
|
2013-10-28 03:56:22 +04:00
|
|
|
|
mergeOptionDecls = loc: opts:
|
2015-07-23 18:19:21 +03:00
|
|
|
|
foldl' (res: opt:
|
2016-09-07 04:03:32 +03:00
|
|
|
|
let t = res.type;
|
|
|
|
|
t' = opt.options.type;
|
|
|
|
|
mergedType = t.typeMerge t'.functor;
|
|
|
|
|
typesMergeable = mergedType != null;
|
|
|
|
|
typeSet = if (bothHave "type") && typesMergeable
|
|
|
|
|
then { type = mergedType; }
|
|
|
|
|
else {};
|
|
|
|
|
bothHave = k: opt.options ? ${k} && res ? ${k};
|
|
|
|
|
in
|
|
|
|
|
if bothHave "default" ||
|
|
|
|
|
bothHave "example" ||
|
|
|
|
|
bothHave "description" ||
|
|
|
|
|
bothHave "apply" ||
|
|
|
|
|
(bothHave "type" && (! typesMergeable))
|
2013-10-28 03:56:22 +04:00
|
|
|
|
then
|
2013-10-28 22:48:30 +04:00
|
|
|
|
throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
|
2013-10-28 03:56:22 +04:00
|
|
|
|
else
|
2014-08-29 16:38:19 +04:00
|
|
|
|
let
|
|
|
|
|
/* Add the modules of the current option to the list of modules
|
|
|
|
|
already collected. The options attribute except either a list of
|
|
|
|
|
submodules or a submodule. For each submodule, we add the file of the
|
|
|
|
|
current option declaration as the file use for the submodule. If the
|
|
|
|
|
submodule defines any filename, then we ignore the enclosing option file. */
|
|
|
|
|
options' = toList opt.options.options;
|
|
|
|
|
coerceOption = file: opt:
|
2015-03-12 01:30:30 +03:00
|
|
|
|
if isFunction opt then packSubmodule file opt
|
|
|
|
|
else packSubmodule file { options = opt; };
|
2014-08-29 18:42:44 +04:00
|
|
|
|
getSubModules = opt.options.type.getSubModules or null;
|
2014-08-29 16:38:19 +04:00
|
|
|
|
submodules =
|
2015-03-12 01:30:30 +03:00
|
|
|
|
if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options
|
2014-08-29 18:42:44 +04:00
|
|
|
|
else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options
|
2014-08-29 16:38:19 +04:00
|
|
|
|
else res.options;
|
|
|
|
|
in opt.options // res //
|
2015-07-23 18:19:21 +03:00
|
|
|
|
{ declarations = res.declarations ++ [opt.file];
|
2014-08-29 16:38:19 +04:00
|
|
|
|
options = submodules;
|
2016-09-07 04:03:32 +03:00
|
|
|
|
} // typeSet
|
2013-10-28 17:25:58 +04:00
|
|
|
|
) { inherit loc; declarations = []; options = []; } opts;
|
2013-10-28 03:56:22 +04:00
|
|
|
|
|
|
|
|
|
/* Merge all the definitions of an option to produce the final
|
|
|
|
|
config value. */
|
2016-03-01 22:47:08 +03:00
|
|
|
|
evalOptionValue = loc: opt: defs:
|
2009-09-14 17:19:00 +04:00
|
|
|
|
let
|
2014-05-02 01:29:30 +04:00
|
|
|
|
# Add in the default value for this option, if any.
|
2015-07-30 14:36:57 +03:00
|
|
|
|
defs' =
|
|
|
|
|
(optional (opt ? default)
|
|
|
|
|
{ file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
|
2015-03-13 01:01:47 +03:00
|
|
|
|
|
2015-07-28 15:41:36 +03:00
|
|
|
|
# Handle properties, check types, and merge everything together.
|
2015-07-30 14:36:57 +03:00
|
|
|
|
res =
|
|
|
|
|
if opt.readOnly or false && length defs' > 1 then
|
|
|
|
|
throw "The option `${showOption loc}' is read-only, but it's set multiple times."
|
|
|
|
|
else
|
2016-03-01 22:47:08 +03:00
|
|
|
|
mergeDefinitions loc opt.type defs';
|
2015-07-28 15:41:36 +03:00
|
|
|
|
|
|
|
|
|
# Check whether the option is defined, and apply the ‘apply’
|
|
|
|
|
# function to the merged value. This allows options to yield a
|
|
|
|
|
# value computed from the definitions.
|
|
|
|
|
value =
|
|
|
|
|
if !res.isDefined then
|
|
|
|
|
throw "The option `${showOption loc}' is used but not defined."
|
|
|
|
|
else if opt ? apply then
|
|
|
|
|
opt.apply res.mergedValue
|
|
|
|
|
else
|
|
|
|
|
res.mergedValue;
|
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
in opt //
|
2013-10-28 18:13:51 +04:00
|
|
|
|
{ value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
2015-08-05 15:29:38 +03:00
|
|
|
|
definitions = map (def: def.value) res.defsFinal;
|
2015-07-28 15:41:36 +03:00
|
|
|
|
files = map (def: def.file) res.defsFinal;
|
|
|
|
|
inherit (res) isDefined;
|
2011-04-27 22:41:37 +04:00
|
|
|
|
};
|
|
|
|
|
|
2015-07-28 15:29:29 +03:00
|
|
|
|
# Merge definitions of a value of a given type.
|
2016-03-01 22:47:08 +03:00
|
|
|
|
mergeDefinitions = loc: type: defs: rec {
|
2015-07-28 15:29:29 +03:00
|
|
|
|
defsFinal =
|
|
|
|
|
let
|
2015-07-28 15:41:36 +03:00
|
|
|
|
# Process mkMerge and mkIf properties.
|
|
|
|
|
defs' = concatMap (m:
|
2015-07-28 15:29:29 +03:00
|
|
|
|
map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value)
|
|
|
|
|
) defs;
|
|
|
|
|
|
2015-07-28 15:41:36 +03:00
|
|
|
|
# Process mkOverride properties.
|
|
|
|
|
defs'' = filterOverrides defs';
|
2015-07-28 15:29:29 +03:00
|
|
|
|
|
2015-07-28 15:41:36 +03:00
|
|
|
|
# Sort mkOrder properties.
|
|
|
|
|
defs''' =
|
2015-07-28 15:29:29 +03:00
|
|
|
|
# Avoid sorting if we don't have to.
|
2015-07-28 15:41:36 +03:00
|
|
|
|
if any (def: def.value._type or "" == "order") defs''
|
|
|
|
|
then sortProperties defs''
|
|
|
|
|
else defs'';
|
|
|
|
|
in defs''';
|
2015-07-28 15:29:29 +03:00
|
|
|
|
|
|
|
|
|
# Type-check the remaining definitions, and merge them.
|
|
|
|
|
mergedValue = foldl' (res: def:
|
|
|
|
|
if type.check def.value then res
|
2017-02-03 03:15:56 +03:00
|
|
|
|
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.description}.")
|
2016-03-01 22:47:08 +03:00
|
|
|
|
(type.merge loc defsFinal) defsFinal;
|
2015-07-28 15:29:29 +03:00
|
|
|
|
|
|
|
|
|
isDefined = defsFinal != [];
|
|
|
|
|
|
|
|
|
|
optionalValue =
|
|
|
|
|
if isDefined then { value = mergedValue; }
|
|
|
|
|
else {};
|
|
|
|
|
};
|
2014-05-02 01:29:30 +04:00
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
/* Given a config set, expand mkMerge properties, and push down the
|
2014-03-30 22:35:25 +04:00
|
|
|
|
other properties into the children. The result is a list of
|
2013-10-28 03:56:22 +04:00
|
|
|
|
config sets that do not have properties at top-level. For
|
|
|
|
|
example,
|
2009-07-13 20:18:52 +04:00
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
|
2009-09-15 17:36:30 +04:00
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
is transformed into
|
2009-09-15 17:36:30 +04:00
|
|
|
|
|
2014-02-18 00:29:08 +04:00
|
|
|
|
[ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ].
|
2012-11-30 15:56:18 +04:00
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
This transform is the critical step that allows mkIf conditions
|
|
|
|
|
to refer to the full configuration without creating an infinite
|
|
|
|
|
recursion.
|
|
|
|
|
*/
|
|
|
|
|
pushDownProperties = cfg:
|
|
|
|
|
if cfg._type or "" == "merge" then
|
|
|
|
|
concatMap pushDownProperties cfg.contents
|
|
|
|
|
else if cfg._type or "" == "if" then
|
|
|
|
|
map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
|
2013-10-28 20:46:45 +04:00
|
|
|
|
else if cfg._type or "" == "override" then
|
|
|
|
|
map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content)
|
2014-03-30 22:35:25 +04:00
|
|
|
|
else # FIXME: handle mkOrder?
|
2013-10-28 03:56:22 +04:00
|
|
|
|
[ cfg ];
|
|
|
|
|
|
|
|
|
|
/* Given a config value, expand mkMerge properties, and discharge
|
|
|
|
|
any mkIf conditions. That is, this is the place where mkIf
|
|
|
|
|
conditions are actually evaluated. The result is a list of
|
|
|
|
|
config values. For example, ‘mkIf false x’ yields ‘[]’,
|
|
|
|
|
‘mkIf true x’ yields ‘[x]’, and
|
|
|
|
|
|
|
|
|
|
mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
|
|
|
|
|
|
|
|
|
|
yields ‘[ 1 2 ]’.
|
|
|
|
|
*/
|
|
|
|
|
dischargeProperties = def:
|
|
|
|
|
if def._type or "" == "merge" then
|
|
|
|
|
concatMap dischargeProperties def.contents
|
|
|
|
|
else if def._type or "" == "if" then
|
2016-11-21 16:51:57 +03:00
|
|
|
|
if isBool def.condition then
|
|
|
|
|
if def.condition then
|
|
|
|
|
dischargeProperties def.content
|
|
|
|
|
else
|
|
|
|
|
[ ]
|
2013-10-28 03:56:22 +04:00
|
|
|
|
else
|
2016-11-21 16:51:57 +03:00
|
|
|
|
throw "‘mkIf’ called with a non-Boolean condition"
|
2009-10-10 03:03:24 +04:00
|
|
|
|
else
|
2013-10-28 03:56:22 +04:00
|
|
|
|
[ def ];
|
2009-09-15 17:36:30 +04:00
|
|
|
|
|
2013-10-28 10:52:24 +04:00
|
|
|
|
/* Given a list of config values, process the mkOverride properties,
|
|
|
|
|
that is, return the values that have the highest (that is,
|
|
|
|
|
numerically lowest) priority, and strip the mkOverride
|
2013-10-28 03:56:22 +04:00
|
|
|
|
properties. For example,
|
2012-11-30 15:56:18 +04:00
|
|
|
|
|
2013-10-28 10:52:24 +04:00
|
|
|
|
[ { file = "/1"; value = mkOverride 10 "a"; }
|
|
|
|
|
{ file = "/2"; value = mkOverride 20 "b"; }
|
|
|
|
|
{ file = "/3"; value = "z"; }
|
|
|
|
|
{ file = "/4"; value = mkOverride 10 "d"; }
|
2013-10-28 07:46:36 +04:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
yields
|
|
|
|
|
|
2013-10-28 10:52:24 +04:00
|
|
|
|
[ { file = "/1"; value = "a"; }
|
|
|
|
|
{ file = "/4"; value = "d"; }
|
2013-10-28 07:46:36 +04:00
|
|
|
|
]
|
2009-09-15 17:36:30 +04:00
|
|
|
|
|
2013-10-28 07:46:36 +04:00
|
|
|
|
Note that "z" has the default priority 100.
|
2013-10-28 03:56:22 +04:00
|
|
|
|
*/
|
2013-10-30 17:21:41 +04:00
|
|
|
|
filterOverrides = defs:
|
2009-09-15 17:36:30 +04:00
|
|
|
|
let
|
2013-10-28 03:56:22 +04:00
|
|
|
|
defaultPrio = 100;
|
2013-10-28 08:23:10 +04:00
|
|
|
|
getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio;
|
2015-07-23 18:19:21 +03:00
|
|
|
|
highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs;
|
2013-10-28 08:23:10 +04:00
|
|
|
|
strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def;
|
2013-10-28 03:56:22 +04:00
|
|
|
|
in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
|
|
|
|
|
|
2014-03-30 22:35:25 +04:00
|
|
|
|
/* Sort a list of properties. The sort priority of a property is
|
2017-04-19 22:41:28 +03:00
|
|
|
|
1000 by default, but can be overridden by wrapping the property
|
2014-03-30 22:35:25 +04:00
|
|
|
|
using mkOrder. */
|
|
|
|
|
sortProperties = defs:
|
|
|
|
|
let
|
|
|
|
|
strip = def:
|
|
|
|
|
if def.value._type or "" == "order"
|
|
|
|
|
then def // { value = def.value.content; inherit (def.value) priority; }
|
|
|
|
|
else def;
|
|
|
|
|
defs' = map strip defs;
|
|
|
|
|
compare = a: b: (a.priority or 1000) < (b.priority or 1000);
|
|
|
|
|
in sort compare defs';
|
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
/* Hack for backward compatibility: convert options of type
|
2014-08-29 18:42:44 +04:00
|
|
|
|
optionSet to options of type submodule. FIXME: remove
|
|
|
|
|
eventually. */
|
2016-03-01 22:47:08 +03:00
|
|
|
|
fixupOptionType = loc: opt:
|
2009-11-07 04:59:50 +03:00
|
|
|
|
let
|
2014-08-29 16:38:19 +04:00
|
|
|
|
options = opt.options or
|
2014-08-29 20:52:31 +04:00
|
|
|
|
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
|
2013-10-28 03:56:22 +04:00
|
|
|
|
f = tp:
|
2016-09-07 04:03:32 +03:00
|
|
|
|
let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
|
|
|
|
|
in
|
2014-08-29 20:52:31 +04:00
|
|
|
|
if tp.name == "option set" || tp.name == "submodule" then
|
|
|
|
|
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
|
2016-09-07 04:03:32 +03:00
|
|
|
|
else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
|
|
|
|
|
else if optionSetIn "loaOf" then types.loaOf (types.submodule options)
|
|
|
|
|
else if optionSetIn "listOf" then types.listOf (types.submodule options)
|
|
|
|
|
else if optionSetIn "nullOr" then types.nullOr (types.submodule options)
|
2013-10-28 03:56:22 +04:00
|
|
|
|
else tp;
|
2014-08-29 18:42:44 +04:00
|
|
|
|
in
|
|
|
|
|
if opt.type.getSubModules or null == null
|
2016-03-01 22:47:08 +03:00
|
|
|
|
then opt // { type = f (opt.type or types.unspecified); }
|
2014-08-29 18:42:44 +04:00
|
|
|
|
else opt // { type = opt.type.substSubModules opt.options; options = []; };
|
2013-10-28 03:56:22 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Properties. */
|
|
|
|
|
|
|
|
|
|
mkIf = condition: content:
|
|
|
|
|
{ _type = "if";
|
|
|
|
|
inherit condition content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkAssert = assertion: message: content:
|
|
|
|
|
mkIf
|
|
|
|
|
(if assertion then true else throw "\nFailed assertion: ${message}")
|
|
|
|
|
content;
|
|
|
|
|
|
|
|
|
|
mkMerge = contents:
|
|
|
|
|
{ _type = "merge";
|
|
|
|
|
inherit contents;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkOverride = priority: content:
|
|
|
|
|
{ _type = "override";
|
|
|
|
|
inherit priority content;
|
|
|
|
|
};
|
|
|
|
|
|
2013-10-29 16:04:52 +04:00
|
|
|
|
mkOptionDefault = mkOverride 1001; # priority of option defaults
|
|
|
|
|
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
|
2013-10-28 03:56:22 +04:00
|
|
|
|
mkForce = mkOverride 50;
|
2013-10-29 16:04:52 +04:00
|
|
|
|
mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
|
2013-10-28 03:56:22 +04:00
|
|
|
|
|
2014-05-05 14:29:38 +04:00
|
|
|
|
mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0);
|
|
|
|
|
|
2013-10-28 03:56:22 +04:00
|
|
|
|
mkFixStrictness = id; # obsolete, no-op
|
|
|
|
|
|
2014-03-30 22:35:25 +04:00
|
|
|
|
mkOrder = priority: content:
|
|
|
|
|
{ _type = "order";
|
|
|
|
|
inherit priority content;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkBefore = mkOrder 500;
|
|
|
|
|
mkAfter = mkOrder 1500;
|
2009-11-07 04:59:50 +03:00
|
|
|
|
|
2015-10-14 19:05:50 +03:00
|
|
|
|
|
2014-12-23 00:38:38 +03:00
|
|
|
|
# Convenient property used to transfer all definitions and their
|
|
|
|
|
# properties from one option to another. This property is useful for
|
|
|
|
|
# renaming options, and also for including properties from another module
|
|
|
|
|
# system, including sub-modules.
|
|
|
|
|
#
|
|
|
|
|
# { config, options, ... }:
|
|
|
|
|
#
|
|
|
|
|
# {
|
|
|
|
|
# # 'bar' might not always be defined in the current module-set.
|
|
|
|
|
# config.foo.enable = mkAliasDefinitions (options.bar.enable or {});
|
|
|
|
|
#
|
|
|
|
|
# # 'barbaz' has to be defined in the current module-set.
|
|
|
|
|
# config.foobar.paths = mkAliasDefinitions options.barbaz.paths;
|
|
|
|
|
# }
|
|
|
|
|
#
|
|
|
|
|
# Note, this is different than taking the value of the option and using it
|
|
|
|
|
# as a definition, as the new definition will not keep the mkOverride /
|
|
|
|
|
# mkDefault properties of the previous option.
|
|
|
|
|
#
|
|
|
|
|
mkAliasDefinitions = mkAliasAndWrapDefinitions id;
|
|
|
|
|
mkAliasAndWrapDefinitions = wrap: option:
|
|
|
|
|
mkMerge
|
|
|
|
|
(optional (isOption option && option.isDefined)
|
|
|
|
|
(wrap (mkMerge option.definitions)));
|
|
|
|
|
|
2013-10-29 17:23:10 +04:00
|
|
|
|
|
|
|
|
|
/* Compatibility. */
|
2016-03-01 22:47:08 +03:00
|
|
|
|
fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
|
2013-10-29 17:23:10 +04:00
|
|
|
|
|
2015-10-14 19:05:50 +03:00
|
|
|
|
|
|
|
|
|
/* Return a module that causes a warning to be shown if the
|
|
|
|
|
specified option is defined. For example,
|
|
|
|
|
|
2016-03-27 02:01:43 +03:00
|
|
|
|
mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>"
|
2015-10-14 19:05:50 +03:00
|
|
|
|
|
|
|
|
|
causes a warning if the user defines boot.loader.grub.bootDevice.
|
2016-03-27 02:01:43 +03:00
|
|
|
|
|
|
|
|
|
replacementInstructions is a string that provides instructions on
|
|
|
|
|
how to achieve the same functionality without the removed option,
|
|
|
|
|
or alternatively a reasoning why the functionality is not needed.
|
|
|
|
|
replacementInstructions SHOULD be provided!
|
2015-10-14 19:05:50 +03:00
|
|
|
|
*/
|
2016-03-27 02:01:43 +03:00
|
|
|
|
mkRemovedOptionModule = optionName: replacementInstructions:
|
2015-10-14 19:05:50 +03:00
|
|
|
|
{ options, ... }:
|
|
|
|
|
{ options = setAttrByPath optionName (mkOption {
|
|
|
|
|
visible = false;
|
|
|
|
|
});
|
|
|
|
|
config.warnings =
|
|
|
|
|
let opt = getAttrFromPath optionName options; in
|
2016-03-27 02:01:43 +03:00
|
|
|
|
optional opt.isDefined ''
|
|
|
|
|
The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it.
|
|
|
|
|
${replacementInstructions}'';
|
2015-10-14 19:05:50 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Return a module that causes a warning to be shown if the
|
|
|
|
|
specified "from" option is defined; the defined value is however
|
|
|
|
|
forwarded to the "to" option. This can be used to rename options
|
|
|
|
|
while providing backward compatibility. For example,
|
|
|
|
|
|
|
|
|
|
mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ]
|
|
|
|
|
|
|
|
|
|
forwards any definitions of boot.copyKernels to
|
|
|
|
|
boot.loader.grub.copyKernels while printing a warning.
|
|
|
|
|
*/
|
|
|
|
|
mkRenamedOptionModule = from: to: doRename {
|
|
|
|
|
inherit from to;
|
|
|
|
|
visible = false;
|
|
|
|
|
warn = true;
|
|
|
|
|
use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'.";
|
|
|
|
|
};
|
|
|
|
|
|
2016-09-24 15:09:52 +03:00
|
|
|
|
/* Return a module that causes a warning to be shown if any of the "from"
|
|
|
|
|
option is defined; the defined values can be used in the "mergeFn" to set
|
|
|
|
|
the "to" value.
|
|
|
|
|
This function can be used to merge multiple options into one that has a
|
|
|
|
|
different type.
|
|
|
|
|
|
|
|
|
|
"mergeFn" takes the module "config" as a parameter and must return a value
|
|
|
|
|
of "to" option type.
|
|
|
|
|
|
|
|
|
|
mkMergedOptionModule
|
|
|
|
|
[ [ "a" "b" "c" ]
|
|
|
|
|
[ "d" "e" "f" ] ]
|
|
|
|
|
[ "x" "y" "z" ]
|
|
|
|
|
(config:
|
|
|
|
|
let value = p: getAttrFromPath p config;
|
|
|
|
|
in
|
|
|
|
|
if (value [ "a" "b" "c" ]) == true then "foo"
|
|
|
|
|
else if (value [ "d" "e" "f" ]) == true then "bar"
|
|
|
|
|
else "baz")
|
|
|
|
|
|
|
|
|
|
- options.a.b.c is a removed boolean option
|
|
|
|
|
- options.d.e.f is a removed boolean option
|
|
|
|
|
- options.x.y.z is a new str option that combines a.b.c and d.e.f
|
|
|
|
|
functionality
|
|
|
|
|
|
|
|
|
|
This show a warning if any a.b.c or d.e.f is set, and set the value of
|
2017-03-12 01:39:40 +03:00
|
|
|
|
x.y.z to the result of the merge function
|
2016-09-24 15:09:52 +03:00
|
|
|
|
*/
|
|
|
|
|
mkMergedOptionModule = from: to: mergeFn:
|
|
|
|
|
{ config, options, ... }:
|
|
|
|
|
{
|
|
|
|
|
options = foldl recursiveUpdate {} (map (path: setAttrByPath path (mkOption {
|
|
|
|
|
visible = false;
|
|
|
|
|
# To use the value in mergeFn without triggering errors
|
|
|
|
|
default = "_mkMergedOptionModule";
|
|
|
|
|
})) from);
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
|
warnings = filter (x: x != "") (map (f:
|
|
|
|
|
let val = getAttrFromPath f config;
|
|
|
|
|
opt = getAttrFromPath f options;
|
|
|
|
|
in
|
2017-03-12 01:39:40 +03:00
|
|
|
|
optionalString
|
2016-09-24 15:09:52 +03:00
|
|
|
|
(val != "_mkMergedOptionModule")
|
|
|
|
|
"The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly."
|
|
|
|
|
) from);
|
|
|
|
|
} // setAttrByPath to (mkMerge
|
2017-03-12 01:39:40 +03:00
|
|
|
|
(optional
|
2016-09-24 15:09:52 +03:00
|
|
|
|
(any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from)
|
|
|
|
|
(mergeFn config)));
|
|
|
|
|
};
|
|
|
|
|
|
2016-09-24 15:10:29 +03:00
|
|
|
|
/* Single "from" version of mkMergedOptionModule.
|
|
|
|
|
Return a module that causes a warning to be shown if the "from" option is
|
|
|
|
|
defined; the defined value can be used in the "mergeFn" to set the "to"
|
|
|
|
|
value.
|
|
|
|
|
This function can be used to change an option into another that has a
|
|
|
|
|
different type.
|
|
|
|
|
|
|
|
|
|
"mergeFn" takes the module "config" as a parameter and must return a value of
|
|
|
|
|
"to" option type.
|
|
|
|
|
|
|
|
|
|
mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ]
|
|
|
|
|
(config:
|
|
|
|
|
let value = getAttrFromPath [ "a" "b" "c" ] config;
|
|
|
|
|
in
|
|
|
|
|
if value > 100 then "high"
|
|
|
|
|
else "normal")
|
|
|
|
|
|
|
|
|
|
- options.a.b.c is a removed int option
|
|
|
|
|
- options.x.y.z is a new str option that supersedes a.b.c
|
|
|
|
|
|
|
|
|
|
This show a warning if a.b.c is set, and set the value of x.y.z to the
|
|
|
|
|
result of the change function
|
|
|
|
|
*/
|
|
|
|
|
mkChangedOptionModule = from: to: changeFn:
|
|
|
|
|
mkMergedOptionModule [ from ] to changeFn;
|
|
|
|
|
|
2015-10-14 19:05:50 +03:00
|
|
|
|
/* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */
|
|
|
|
|
mkAliasOptionModule = from: to: doRename {
|
|
|
|
|
inherit from to;
|
|
|
|
|
visible = true;
|
|
|
|
|
warn = false;
|
|
|
|
|
use = id;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
doRename = { from, to, visible, warn, use }:
|
|
|
|
|
let
|
|
|
|
|
toOf = attrByPath to
|
|
|
|
|
(abort "Renaming error: option `${showOption to}' does not exists.");
|
|
|
|
|
in
|
|
|
|
|
{ config, options, ... }:
|
|
|
|
|
{ options = setAttrByPath from (mkOption {
|
|
|
|
|
description = "Alias of <option>${showOption to}</option>.";
|
|
|
|
|
apply = x: use (toOf config);
|
|
|
|
|
});
|
|
|
|
|
config = {
|
|
|
|
|
warnings =
|
|
|
|
|
let opt = getAttrFromPath from options; in
|
|
|
|
|
optional (warn && opt.isDefined)
|
|
|
|
|
"The option `${showOption from}' defined in ${showFiles opt.files} has been renamed to `${showOption to}'.";
|
|
|
|
|
} // setAttrByPath to (mkAliasDefinitions (getAttrFromPath from options));
|
|
|
|
|
};
|
|
|
|
|
|
2009-09-15 00:10:41 +04:00
|
|
|
|
}
|