Big cleanup of the NixOS module system
The major changes are: * The evaluation is now driven by the declared options. In particular, this fixes the long-standing problem with lack of laziness of disabled option definitions. Thus, a configuration like config = mkIf false { environment.systemPackages = throw "bla"; }; will now evaluate without throwing an error. This also improves performance since we're not evaluating unused option definitions. * The implementation of properties is greatly simplified. * There is a new type constructor "submodule" that replaces "optionSet". Unlike "optionSet", "submodule" gets its option declarations as an argument, making it more like "listOf" and other type constructors. A typical use is: foo = mkOption { type = type.attrsOf (type.submodule ( { config, ... }: { bar = mkOption { ... }; xyzzy = mkOption { ... }; })); }; Existing uses of "optionSet" are automatically mapped to "submodule". * Modules are now checked for unsupported attributes: you get an error if a module contains an attribute other than "config", "options" or "imports". * The new implementation is faster and uses much less memory.
This commit is contained in:
parent
f4dadc5df8
commit
0e333688ce
@ -8,7 +8,6 @@ let
|
|||||||
sources = import ./sources.nix;
|
sources = import ./sources.nix;
|
||||||
modules = import ./modules.nix;
|
modules = import ./modules.nix;
|
||||||
options = import ./options.nix;
|
options = import ./options.nix;
|
||||||
properties = import ./properties.nix;
|
|
||||||
types = import ./types.nix;
|
types = import ./types.nix;
|
||||||
meta = import ./meta.nix;
|
meta = import ./meta.nix;
|
||||||
debug = import ./debug.nix;
|
debug = import ./debug.nix;
|
||||||
@ -21,13 +20,13 @@ let
|
|||||||
|
|
||||||
in
|
in
|
||||||
{ inherit trivial lists strings stringsWithDeps attrsets sources options
|
{ inherit trivial lists strings stringsWithDeps attrsets sources options
|
||||||
properties modules types meta debug maintainers licenses platforms systems;
|
modules types meta debug maintainers licenses platforms systems;
|
||||||
# Pull in some builtins not included elsewhere.
|
# Pull in some builtins not included elsewhere.
|
||||||
inherit (builtins) pathExists readFile;
|
inherit (builtins) pathExists readFile;
|
||||||
}
|
}
|
||||||
# !!! don't include everything at top-level; perhaps only the most
|
# !!! don't include everything at top-level; perhaps only the most
|
||||||
# commonly used functions.
|
# commonly used functions.
|
||||||
// trivial // lists // strings // stringsWithDeps // attrsets // sources
|
// trivial // lists // strings // stringsWithDeps // attrsets // sources
|
||||||
// properties // options // types // meta // debug // misc // modules
|
// options // types // meta // debug // misc // modules
|
||||||
// systems
|
// systems
|
||||||
// customisation
|
// customisation
|
||||||
|
@ -166,9 +166,10 @@ in rec {
|
|||||||
zipLists = zipListsWith (fst: snd: { inherit fst snd; });
|
zipLists = zipListsWith (fst: snd: { inherit fst snd; });
|
||||||
|
|
||||||
|
|
||||||
# Reverse the order of the elements of a list.
|
# Reverse the order of the elements of a list. FIXME: O(n^2)!
|
||||||
reverseList = fold (e: acc: acc ++ [ e ]) [];
|
reverseList = fold (e: acc: acc ++ [ e ]) [];
|
||||||
|
|
||||||
|
|
||||||
# Sort a list based on a comparator function which compares two
|
# Sort a list based on a comparator function which compares two
|
||||||
# elements and returns true if the first argument is strictly below
|
# elements and returns true if the first argument is strictly below
|
||||||
# the second argument. The returned list is sorted in an increasing
|
# the second argument. The returned list is sorted in an increasing
|
||||||
|
556
lib/modules.nix
556
lib/modules.nix
@ -1,379 +1,245 @@
|
|||||||
# NixOS module handling.
|
with import ./.. {};
|
||||||
|
with lib;
|
||||||
let lib = import ./default.nix; in
|
|
||||||
|
|
||||||
with { inherit (builtins) head; };
|
|
||||||
with import ./trivial.nix;
|
|
||||||
with import ./lists.nix;
|
|
||||||
with import ./misc.nix;
|
|
||||||
with import ./attrsets.nix;
|
|
||||||
with import ./options.nix;
|
|
||||||
with import ./properties.nix;
|
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
# Unfortunately this can also be a string.
|
/* Evaluate a set of modules. The result is a set of two
|
||||||
isPath = x: !(
|
attributes: ‘options’: the nested set of all option declarations,
|
||||||
builtins.isFunction x
|
and ‘config’: the nested set of all option values. */
|
||||||
|| builtins.isAttrs x
|
evalModules = modules: args:
|
||||||
|| builtins.isInt x
|
|
||||||
|| builtins.isBool x
|
|
||||||
|| builtins.isList x
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
importIfPath = path:
|
|
||||||
if isPath path then
|
|
||||||
import path
|
|
||||||
else
|
|
||||||
path;
|
|
||||||
|
|
||||||
|
|
||||||
applyIfFunction = f: arg:
|
|
||||||
if builtins.isFunction f then
|
|
||||||
f arg
|
|
||||||
else
|
|
||||||
f;
|
|
||||||
|
|
||||||
|
|
||||||
isModule = m:
|
|
||||||
(m ? config && isAttrs m.config && ! isOption m.config)
|
|
||||||
|| (m ? options && isAttrs m.options && ! isOption m.options);
|
|
||||||
|
|
||||||
|
|
||||||
# Convert module to a set which has imports / options and config
|
|
||||||
# attributes.
|
|
||||||
unifyModuleSyntax = m:
|
|
||||||
let
|
let
|
||||||
delayedModule = delayProperties m;
|
args' = args // result;
|
||||||
|
closed = closeModules modules args';
|
||||||
getImports =
|
# Note: the list of modules is reversed to maintain backward
|
||||||
toList (rmProperties (delayedModule.require or []));
|
# compatibility with the old module system. Not sure if this is
|
||||||
getImportedPaths = filter isPath getImports;
|
# the most sensible policy.
|
||||||
getImportedSets = filter (x: !isPath x) getImports;
|
options = mergeModules (reverseList closed);
|
||||||
|
config = yieldConfig options;
|
||||||
getConfig =
|
yieldConfig = mapAttrs (n: v: if isOption v then v.value else yieldConfig v);
|
||||||
removeAttrs delayedModule ["require" "key" "imports"];
|
result = { inherit options config; };
|
||||||
|
in result;
|
||||||
|
|
||||||
|
/* Close a set of modules under the ‘imports’ relation. */
|
||||||
|
closeModules = modules: args:
|
||||||
|
let
|
||||||
|
coerceToModule = n: x:
|
||||||
|
if isAttrs x || builtins.isFunction x then
|
||||||
|
unifyModuleSyntax "anon-${toString n}" (applyIfFunction x args)
|
||||||
|
else
|
||||||
|
unifyModuleSyntax (toString x) (applyIfFunction (import x) args);
|
||||||
|
toClosureList = imap (path: coerceToModule path);
|
||||||
in
|
in
|
||||||
if isModule m then
|
builtins.genericClosure {
|
||||||
{ key = "<unknown location>"; } // m
|
startSet = toClosureList modules;
|
||||||
else
|
operator = m: toClosureList m.imports;
|
||||||
{ key = "<unknown location>";
|
|
||||||
imports = (m.imports or []) ++ getImportedPaths;
|
|
||||||
config = getConfig;
|
|
||||||
} // (
|
|
||||||
if getImportedSets != [] then
|
|
||||||
assert length getImportedSets == 1;
|
|
||||||
{ options = head getImportedSets; }
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
unifyOptionModule = {key ? "<unknown location>"}: name: index: m: (args:
|
|
||||||
let
|
|
||||||
module = lib.applyIfFunction m args;
|
|
||||||
key_ = rec {
|
|
||||||
file = key;
|
|
||||||
option = name;
|
|
||||||
number = index;
|
|
||||||
outPath = key;
|
|
||||||
};
|
|
||||||
in if lib.isModule module then
|
|
||||||
{ key = key_; } // module
|
|
||||||
else
|
|
||||||
{ key = key_; options = module; }
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
moduleClosure = initModules: args:
|
|
||||||
let
|
|
||||||
moduleImport = origin: index: m:
|
|
||||||
let m' = applyIfFunction (importIfPath m) args;
|
|
||||||
in (unifyModuleSyntax m') // {
|
|
||||||
# used by generic closure to avoid duplicated imports.
|
|
||||||
key =
|
|
||||||
if isPath m then m
|
|
||||||
else m'.key or (newModuleName origin index);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getImports = m: m.imports or [];
|
/* Massage a module into canonical form, that is, a set consisting
|
||||||
|
of ‘options’, ‘config’ and ‘imports’ attributes. */
|
||||||
newModuleName = origin: index:
|
unifyModuleSyntax = key: m:
|
||||||
"${origin.key}:<import-${toString index}>";
|
if m ? config || m ? options || m ? imports then
|
||||||
|
let badAttrs = removeAttrs m ["imports" "options" "config"]; in
|
||||||
topLevel = {
|
if badAttrs != {} then
|
||||||
key = "<top-level>";
|
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. ${builtins.toXML m} "
|
||||||
|
else
|
||||||
|
{ inherit key;
|
||||||
|
imports = m.imports or [];
|
||||||
|
options = m.options or {};
|
||||||
|
config = m.config or {};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ inherit key;
|
||||||
|
imports = m.require or [];
|
||||||
|
options = {};
|
||||||
|
config = m;
|
||||||
};
|
};
|
||||||
|
|
||||||
in
|
applyIfFunction = f: arg: if builtins.isFunction f then f arg else f;
|
||||||
(lazyGenericClosure {
|
|
||||||
startSet = imap (moduleImport topLevel) initModules;
|
|
||||||
operator = m: imap (moduleImport m) (getImports m);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
mergeModules = modules:
|
||||||
|
mergeModules' [] (map (m: m.options) modules) (concatMap (m: pushDownProperties m.config) modules);
|
||||||
|
|
||||||
moduleApply = funs: module:
|
mergeModules' = loc: options: configs:
|
||||||
lib.mapAttrs (name: value:
|
zipAttrsWith (name: vals:
|
||||||
if builtins.hasAttr name funs then
|
let loc' = loc ++ [name]; in
|
||||||
let fun = lib.getAttr name funs; in
|
if all isOption vals then
|
||||||
fun value
|
let opt = fixupOptionType loc' (mergeOptionDecls loc' vals);
|
||||||
|
in evalOptionValue loc' opt (catAttrs name configs)
|
||||||
|
else if any isOption vals then
|
||||||
|
throw "There are options with the prefix `${showOption loc'}', which is itself an option."
|
||||||
else
|
else
|
||||||
value
|
mergeModules' loc' vals (concatMap pushDownProperties (catAttrs name configs))
|
||||||
) module;
|
) options;
|
||||||
|
|
||||||
|
/* Merge multiple option declarations into a single declaration. In
|
||||||
# Handle mkMerge function left behind after a delay property.
|
general, there should be only one declaration of each option.
|
||||||
moduleFlattenMerge = module:
|
The exception is the ‘options’ attribute, which specifies
|
||||||
if module ? config &&
|
sub-options. These can be specified multiple times to allow one
|
||||||
isProperty module.config &&
|
module to add sub-options to an option declared somewhere else
|
||||||
isMerge module.config.property
|
(e.g. multiple modules define sub-options for ‘fileSystems’). */
|
||||||
|
mergeOptionDecls = loc: opts:
|
||||||
|
fold (opt1: opt2:
|
||||||
|
if opt1 ? default && opt2 ? default ||
|
||||||
|
opt1 ? example && opt2 ? example ||
|
||||||
|
opt1 ? description && opt2 ? description ||
|
||||||
|
opt1 ? merge && opt2 ? merge ||
|
||||||
|
opt1 ? apply && opt2 ? apply ||
|
||||||
|
opt1 ? type && opt2 ? type
|
||||||
then
|
then
|
||||||
(map (cfg: { key = module.key; config = cfg; }) module.config.content)
|
throw "Conflicting declarations of the option `${showOption loc}'."
|
||||||
++ [ (module // { config = {}; }) ]
|
|
||||||
else
|
else
|
||||||
[ module ];
|
opt1 // opt2
|
||||||
|
// optionalAttrs (opt1 ? options && opt2 ? options)
|
||||||
|
{ options = [ opt1.options opt2.options ]; }
|
||||||
|
) {} opts;
|
||||||
|
|
||||||
|
/* Merge all the definitions of an option to produce the final
|
||||||
# Handle mkMerge attributes which are left behind by previous delay
|
config value. */
|
||||||
# properties and convert them into a list of modules. Delay properties
|
evalOptionValue = loc: opt: defs:
|
||||||
# inside the config attribute of a module and create a second module if a
|
|
||||||
# mkMerge attribute was left behind.
|
|
||||||
#
|
|
||||||
# Module -> [ Module ]
|
|
||||||
delayModule = module:
|
|
||||||
map (moduleApply { config = delayProperties; }) (moduleFlattenMerge module);
|
|
||||||
|
|
||||||
|
|
||||||
evalDefinitions = opt: values:
|
|
||||||
if opt.type.delayOnGlobalEval or false then
|
|
||||||
map (delayPropertiesWithIter opt.type.iter opt.name)
|
|
||||||
(evalLocalProperties values)
|
|
||||||
else
|
|
||||||
evalProperties values;
|
|
||||||
|
|
||||||
|
|
||||||
selectModule = name: m:
|
|
||||||
{ inherit (m) key;
|
|
||||||
} // (
|
|
||||||
if m ? options && builtins.hasAttr name m.options then
|
|
||||||
{ options = lib.getAttr name m.options; }
|
|
||||||
else {}
|
|
||||||
) // (
|
|
||||||
if m ? config && builtins.hasAttr name m.config then
|
|
||||||
{ config = lib.getAttr name m.config; }
|
|
||||||
else {}
|
|
||||||
);
|
|
||||||
|
|
||||||
filterModules = name: modules:
|
|
||||||
filter (m: m ? config || m ? options) (
|
|
||||||
map (selectModule name) modules
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
modulesNames = modules:
|
|
||||||
lib.concatMap (m: []
|
|
||||||
++ optionals (m ? options) (lib.attrNames m.options)
|
|
||||||
++ optionals (m ? config) (lib.attrNames m.config)
|
|
||||||
) modules;
|
|
||||||
|
|
||||||
|
|
||||||
moduleZip = funs: modules:
|
|
||||||
lib.mapAttrs (name: fun:
|
|
||||||
fun (catAttrs name modules)
|
|
||||||
) funs;
|
|
||||||
|
|
||||||
|
|
||||||
moduleMerge = path: modules_:
|
|
||||||
let
|
let
|
||||||
addName = name:
|
# Process mkMerge and mkIf properties.
|
||||||
if path == "" then name else path + "." + name;
|
defs' = concatMap dischargeProperties defs;
|
||||||
|
# Process mkOverride properties, adding in the default
|
||||||
modules = concatLists (map delayModule modules_);
|
# value specified in the option declaration (if any).
|
||||||
|
defsFinal = filterOverrides (optional (opt ? default) (mkOptionDefault opt.default) ++ defs');
|
||||||
modulesOf = name: filterModules name modules;
|
# Type-check the remaining definitions, and merge them
|
||||||
declarationsOf = name: filter (m: m ? options) (modulesOf name);
|
# if possible.
|
||||||
definitionsOf = name: filter (m: m ? config ) (modulesOf name);
|
merged =
|
||||||
|
if defsFinal == [] then
|
||||||
recurseInto = name:
|
throw "The option `${showOption loc}' is used but not defined."
|
||||||
moduleMerge (addName name) (modulesOf name);
|
else
|
||||||
|
if all opt.type.check defsFinal then
|
||||||
recurseForOption = name: modules: args:
|
opt.type.merge defsFinal
|
||||||
moduleMerge name (
|
#throw "The option `${showOption loc}' has multiple values (with no way to merge them)."
|
||||||
moduleClosure modules args
|
else
|
||||||
);
|
throw "A value of the option `${showOption loc}' has a bad type.";
|
||||||
|
# Finally, apply the ‘apply’ function to the merged
|
||||||
errorSource = modules:
|
# value. This allows options to yield a value computed
|
||||||
"The error may come from the following files:\n" + (
|
# from the definitions.
|
||||||
lib.concatStringsSep "\n" (
|
value = (opt.apply or id) merged;
|
||||||
map (m:
|
in opt //
|
||||||
if m ? key then toString m.key else "<unknown location>"
|
{ inherit value;
|
||||||
) modules
|
definitions = defsFinal;
|
||||||
)
|
isDefined = defsFinal != [];
|
||||||
);
|
|
||||||
|
|
||||||
eol = "\n";
|
|
||||||
|
|
||||||
allNames = modulesNames modules;
|
|
||||||
|
|
||||||
getResults = m:
|
|
||||||
let fetchResult = s: mapAttrs (n: v: v.result) s; in {
|
|
||||||
options = fetchResult m.options;
|
|
||||||
config = fetchResult m.config;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
endRecursion = { options = {}; config = {}; };
|
/* Given a config set, expand mkMerge properties, and push down the
|
||||||
|
mkIf properties into the children. The result is a list of
|
||||||
|
config sets that do not have properties at top-level. For
|
||||||
|
example,
|
||||||
|
|
||||||
in if modules == [] then endRecursion else
|
mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ]
|
||||||
getResults (fix (crossResults: moduleZip {
|
|
||||||
options = lib.zipWithNames allNames (name: values: rec {
|
|
||||||
config = lib.getAttr name crossResults.config;
|
|
||||||
|
|
||||||
declarations = declarationsOf name;
|
is transformed into
|
||||||
declarationSources =
|
|
||||||
map (m: {
|
|
||||||
source = m.key;
|
|
||||||
}) declarations;
|
|
||||||
|
|
||||||
hasOptions = values != [];
|
[ { boot = set1; } { boot = mkIf cond set2; services mkIf cond set3; } ].
|
||||||
isOption = any lib.isOption values;
|
|
||||||
|
|
||||||
decls = # add location to sub-module options.
|
This transform is the critical step that allows mkIf conditions
|
||||||
map (m:
|
to refer to the full configuration without creating an infinite
|
||||||
mapSubOptions
|
recursion.
|
||||||
(unifyOptionModule {inherit (m) key;} name)
|
*/
|
||||||
m.options
|
pushDownProperties = cfg:
|
||||||
) declarations;
|
if cfg._type or "" == "merge" then
|
||||||
|
concatMap pushDownProperties cfg.contents
|
||||||
decl =
|
else if cfg._type or "" == "if" then
|
||||||
lib.addErrorContext "${eol
|
map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content)
|
||||||
}while enhancing option `${addName name}':${eol
|
|
||||||
}${errorSource declarations}${eol
|
|
||||||
}" (
|
|
||||||
addOptionMakeUp
|
|
||||||
{ name = addName name; recurseInto = recurseForOption; }
|
|
||||||
(mergeOptionDecls decls)
|
|
||||||
);
|
|
||||||
|
|
||||||
value = decl // (with config; {
|
|
||||||
inherit (config) isNotDefined;
|
|
||||||
isDefined = ! isNotDefined;
|
|
||||||
declarations = declarationSources;
|
|
||||||
definitions = definitionSources;
|
|
||||||
config = strictResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
recurse = (recurseInto name).options;
|
|
||||||
|
|
||||||
result =
|
|
||||||
if isOption then value
|
|
||||||
else if !hasOptions then {}
|
|
||||||
else if all isAttrs values then recurse
|
|
||||||
else
|
else
|
||||||
throw "${eol
|
# FIXME: handle mkOverride?
|
||||||
}Unexpected type where option declarations are expected.${eol
|
[ cfg ];
|
||||||
}${errorSource declarations}${eol
|
|
||||||
}";
|
|
||||||
|
|
||||||
});
|
/* 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
|
||||||
|
|
||||||
config = lib.zipWithNames allNames (name: values_: rec {
|
mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ]
|
||||||
option = lib.getAttr name crossResults.options;
|
|
||||||
|
|
||||||
definitions = definitionsOf name;
|
yields ‘[ 1 2 ]’.
|
||||||
definitionSources =
|
*/
|
||||||
map (m: {
|
dischargeProperties = def:
|
||||||
source = m.key;
|
if def._type or "" == "merge" then
|
||||||
value = m.config;
|
concatMap dischargeProperties def.contents
|
||||||
}) definitions;
|
else if def._type or "" == "if" then
|
||||||
|
if def.condition then
|
||||||
|
dischargeProperties def.content
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
else
|
||||||
|
[ def ];
|
||||||
|
|
||||||
values = values_ ++
|
/* Given a list of config value, process the mkOverride properties,
|
||||||
optionals (option.isOption && option.decl ? extraConfigs)
|
that is, return the values that have the highest (that
|
||||||
option.decl.extraConfigs;
|
is,. numerically lowest) priority, and strip the mkOverride
|
||||||
|
properties. For example,
|
||||||
|
|
||||||
defs = evalDefinitions option.decl values;
|
[ (mkOverride 10 "a") (mkOverride 20 "b") "z" (mkOverride 10 "d") ]
|
||||||
|
|
||||||
isNotDefined = defs == [];
|
yields ‘[ "a" "d" ]’. Note that "z" has the default priority 100.
|
||||||
|
*/
|
||||||
value =
|
filterOverrides = defs:
|
||||||
lib.addErrorContext "${eol
|
|
||||||
}while evaluating the option `${addName name}':${eol
|
|
||||||
}${errorSource (modulesOf name)}${eol
|
|
||||||
}" (
|
|
||||||
let opt = option.decl; in
|
|
||||||
opt.apply (
|
|
||||||
if isNotDefined then
|
|
||||||
opt.default or (throw "Option `${addName name}' not defined and does not have a default value.")
|
|
||||||
else opt.merge defs
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
strictResult = builtins.tryEval (builtins.toXML value);
|
|
||||||
|
|
||||||
recurse = (recurseInto name).config;
|
|
||||||
|
|
||||||
configIsAnOption = v: isOption (rmProperties v);
|
|
||||||
errConfigIsAnOption =
|
|
||||||
let badModules = filter (m: configIsAnOption m.config) definitions; in
|
|
||||||
"${eol
|
|
||||||
}Option ${addName name} is defined in the configuration section.${eol
|
|
||||||
}${errorSource badModules}${eol
|
|
||||||
}";
|
|
||||||
|
|
||||||
errDefinedWithoutDeclaration =
|
|
||||||
let badModules = definitions; in
|
|
||||||
"${eol
|
|
||||||
}Option '${addName name}' defined without option declaration.${eol
|
|
||||||
}${errorSource badModules}${eol
|
|
||||||
}";
|
|
||||||
|
|
||||||
result =
|
|
||||||
if option.isOption then value
|
|
||||||
else if !option.hasOptions then throw errDefinedWithoutDeclaration
|
|
||||||
else if any configIsAnOption values then throw errConfigIsAnOption
|
|
||||||
else if all isAttrs values then recurse
|
|
||||||
# plain value during the traversal
|
|
||||||
else throw errDefinedWithoutDeclaration;
|
|
||||||
|
|
||||||
});
|
|
||||||
} modules));
|
|
||||||
|
|
||||||
|
|
||||||
fixMergeModules = initModules: {...}@args:
|
|
||||||
lib.fix (result:
|
|
||||||
# This trick avoids an infinite loop because names of attribute
|
|
||||||
# are know and it is not required to evaluate the result of
|
|
||||||
# moduleMerge to know which attributes are present as arguments.
|
|
||||||
let module = { inherit (result) options config; }; in
|
|
||||||
moduleMerge "" (
|
|
||||||
moduleClosure initModules (module // args)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
# Visit all definitions to raise errors related to undeclared options.
|
|
||||||
checkModule = path: {config, options, ...}@m:
|
|
||||||
let
|
let
|
||||||
eol = "\n";
|
defaultPrio = 100;
|
||||||
addName = name:
|
getPrio = def: if def._type or "" == "override" then def.priority else defaultPrio;
|
||||||
if path == "" then name else path + "." + name;
|
min = x: y: if x < y then x else y;
|
||||||
in
|
highestPrio = fold (def: prio: min (getPrio def) prio) 9999 defs;
|
||||||
if lib.isOption options then
|
strip = def: if def._type or "" == "override" then def.content else def;
|
||||||
if options ? options then
|
in concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs;
|
||||||
options.type.fold
|
|
||||||
(cfg: res: res && checkModule (options.type.docPath path) cfg._args)
|
/* Hack for backward compatibility: convert options of type
|
||||||
true config
|
optionSet to configOf. FIXME: remove eventually. */
|
||||||
else
|
fixupOptionType = loc: opt:
|
||||||
true
|
let
|
||||||
else if isAttrs options && lib.attrNames m.options != [] then
|
options' = opt.options or
|
||||||
all (name:
|
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute.");
|
||||||
lib.addErrorContext "${eol
|
coerce = x:
|
||||||
}while checking the attribute `${addName name}':${eol
|
if builtins.isFunction x then x
|
||||||
}" (checkModule (addName name) (selectModule name m))
|
else { config, ... }: { options = x; };
|
||||||
) (lib.attrNames m.config)
|
options = map coerce (flatten options');
|
||||||
else
|
f = tp:
|
||||||
builtins.trace "try to evaluate config ${lib.showVal config}."
|
if tp.name == "option set" then types.submodule options
|
||||||
false;
|
else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options)
|
||||||
|
else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options)
|
||||||
|
else if tp.name == "list of option sets" then types.listOf (types.submodule options)
|
||||||
|
else if tp.name == "null or option set" then types.nullOr (types.submodule options)
|
||||||
|
else tp;
|
||||||
|
in opt // { type = f (opt.type or types.unspecified); };
|
||||||
|
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
mkOptionDefault = mkOverride 1001;
|
||||||
|
mkDefault = mkOverride 1000;
|
||||||
|
mkForce = mkOverride 50;
|
||||||
|
|
||||||
|
mkFixStrictness = id; # obsolete, no-op
|
||||||
|
|
||||||
|
# FIXME: Add mkOrder back in. It's not currently used anywhere in
|
||||||
|
# NixOS, but it should be useful.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
let lib = import ./default.nix; in
|
let lib = import ./default.nix; in
|
||||||
|
|
||||||
with { inherit (builtins) head length; };
|
|
||||||
with import ./trivial.nix;
|
with import ./trivial.nix;
|
||||||
with import ./lists.nix;
|
with import ./lists.nix;
|
||||||
with import ./misc.nix;
|
with import ./misc.nix;
|
||||||
with import ./attrsets.nix;
|
with import ./attrsets.nix;
|
||||||
with import ./properties.nix;
|
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
@ -137,46 +135,6 @@ rec {
|
|||||||
handleOptionSets
|
handleOptionSets
|
||||||
];
|
];
|
||||||
|
|
||||||
# Merge a list of options containning different field. This is useful to
|
|
||||||
# separate the merge & apply fields from the interface.
|
|
||||||
mergeOptionDecls = opts:
|
|
||||||
if opts == [] then {}
|
|
||||||
else if length opts == 1 then
|
|
||||||
let opt = head opts; in
|
|
||||||
if opt ? options then
|
|
||||||
opt // { options = toList opt.options; }
|
|
||||||
else
|
|
||||||
opt
|
|
||||||
else
|
|
||||||
fold (opt1: opt2:
|
|
||||||
lib.addErrorContext "opt1 = ${lib.showVal opt1}\nopt2 = ${lib.showVal opt2}" (
|
|
||||||
# You cannot merge if two options have the same field.
|
|
||||||
assert opt1 ? default -> ! opt2 ? default;
|
|
||||||
assert opt1 ? example -> ! opt2 ? example;
|
|
||||||
assert opt1 ? description -> ! opt2 ? description;
|
|
||||||
assert opt1 ? merge -> ! opt2 ? merge;
|
|
||||||
assert opt1 ? apply -> ! opt2 ? apply;
|
|
||||||
assert opt1 ? type -> ! opt2 ? type;
|
|
||||||
opt1 // opt2
|
|
||||||
// optionalAttrs (opt1 ? options || opt2 ? options) {
|
|
||||||
options =
|
|
||||||
(toList (opt1.options or []))
|
|
||||||
++ (toList (opt2.options or []));
|
|
||||||
}
|
|
||||||
// optionalAttrs (opt1 ? extraConfigs || opt2 ? extraConfigs) {
|
|
||||||
extraConfigs = opt1.extraConfigs or [] ++ opt2.extraConfigs or [];
|
|
||||||
}
|
|
||||||
// optionalAttrs (opt1 ? extraArgs || opt2 ? extraArgs) {
|
|
||||||
extraArgs = opt1.extraArgs or {} // opt2.extraArgs or {};
|
|
||||||
}
|
|
||||||
// optionalAttrs (opt1 ? individualExtraArgs || opt2 ? individualExtraArgs) {
|
|
||||||
individualExtraArgs = zipAttrsWith (name: values:
|
|
||||||
if length values == 1 then head values else (head values // (head (tail values)))
|
|
||||||
) [ (opt1.individualExtraArgs or {}) (opt2.individualExtraArgs or {}) ];
|
|
||||||
}
|
|
||||||
)) {} opts;
|
|
||||||
|
|
||||||
|
|
||||||
# !!! This function will be removed because this can be done with the
|
# !!! This function will be removed because this can be done with the
|
||||||
# multiple option declarations.
|
# multiple option declarations.
|
||||||
addDefaultOptionValues = defs: opts: opts //
|
addDefaultOptionValues = defs: opts: opts //
|
||||||
@ -285,6 +243,7 @@ rec {
|
|||||||
else
|
else
|
||||||
[];
|
[];
|
||||||
in
|
in
|
||||||
|
# FIXME: expensive (O(n^2)
|
||||||
[ docOption ] ++ subOptions ++ rest
|
[ docOption ] ++ subOptions ++ rest
|
||||||
) [] options;
|
) [] options;
|
||||||
|
|
||||||
@ -308,4 +267,7 @@ rec {
|
|||||||
literalExample = text: { _type = "literalExample"; inherit text; };
|
literalExample = text: { _type = "literalExample"; inherit text; };
|
||||||
|
|
||||||
|
|
||||||
|
/* Helper functions. */
|
||||||
|
showOption = concatStringsSep ".";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,464 +0,0 @@
|
|||||||
# Nixpkgs/NixOS properties. Generalize the problem of delayable (not yet
|
|
||||||
# evaluable) properties like mkIf.
|
|
||||||
|
|
||||||
let lib = import ./default.nix; in
|
|
||||||
|
|
||||||
with { inherit (builtins) head tail; };
|
|
||||||
with import ./trivial.nix;
|
|
||||||
with import ./lists.nix;
|
|
||||||
with import ./misc.nix;
|
|
||||||
with import ./attrsets.nix;
|
|
||||||
|
|
||||||
rec {
|
|
||||||
|
|
||||||
inherit (lib) isType;
|
|
||||||
|
|
||||||
# Tell that nothing is defined. When properties are evaluated, this type
|
|
||||||
# is used to remove an entry. Thus if your property evaluation semantic
|
|
||||||
# implies that you have to mute the content of an attribute, then your
|
|
||||||
# property should produce this value.
|
|
||||||
isNotdef = isType "notdef";
|
|
||||||
mkNotdef = {_type = "notdef";};
|
|
||||||
|
|
||||||
# General property type, it has a property attribute and a content
|
|
||||||
# attribute. The property attribute refers to an attribute set which
|
|
||||||
# contains a _type attribute and a list of functions which are used to
|
|
||||||
# evaluate this property. The content attribute is used to stack properties
|
|
||||||
# on top of each other.
|
|
||||||
#
|
|
||||||
# The optional functions which may be contained in the property attribute
|
|
||||||
# are:
|
|
||||||
# - onDelay: run on a copied property.
|
|
||||||
# - onGlobalDelay: run on all copied properties.
|
|
||||||
# - onEval: run on an evaluated property.
|
|
||||||
# - onGlobalEval: run on a list of property stack on top of their values.
|
|
||||||
isProperty = isType "property";
|
|
||||||
mkProperty = p@{property, content, ...}: p // {
|
|
||||||
_type = "property";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Go through the stack of properties and apply the function `op' on all
|
|
||||||
# property and call the function `nul' on the final value which is not a
|
|
||||||
# property. The stack is traversed in reversed order. The `op' function
|
|
||||||
# should expect a property with a content which have been modified.
|
|
||||||
#
|
|
||||||
# Warning: The `op' function expects only one argument in order to avoid
|
|
||||||
# calls to mkProperties as the argument is already a valid property which
|
|
||||||
# contains the result of the folding inside the content attribute.
|
|
||||||
foldProperty = op: nul: attrs:
|
|
||||||
if isProperty attrs then
|
|
||||||
op (attrs // {
|
|
||||||
content = foldProperty op nul attrs.content;
|
|
||||||
})
|
|
||||||
else
|
|
||||||
nul attrs;
|
|
||||||
|
|
||||||
# Simple function which can be used as the `op' argument of the
|
|
||||||
# foldProperty function. Properties that you don't want to handle can be
|
|
||||||
# ignored with the `id' function. `isSearched' is a function which should
|
|
||||||
# check the type of a property and return a boolean value. `thenFun' and
|
|
||||||
# `elseFun' are functions which behave as the `op' argument of the
|
|
||||||
# foldProperty function.
|
|
||||||
foldFilter = isSearched: thenFun: elseFun: attrs:
|
|
||||||
if isSearched attrs.property then
|
|
||||||
thenFun attrs
|
|
||||||
else
|
|
||||||
elseFun attrs;
|
|
||||||
|
|
||||||
|
|
||||||
# Move properties from the current attribute set to the attribute
|
|
||||||
# contained in this attribute set. This trigger property handlers called
|
|
||||||
# `onDelay' and `onGlobalDelay'.
|
|
||||||
delayPropertiesWithIter = iter: path: attrs:
|
|
||||||
let cleanAttrs = rmProperties attrs; in
|
|
||||||
if isProperty attrs then
|
|
||||||
iter (a: v:
|
|
||||||
lib.addErrorContext "while moving properties on the attribute `${a}':" (
|
|
||||||
triggerPropertiesGlobalDelay a (
|
|
||||||
triggerPropertiesDelay a (
|
|
||||||
copyProperties attrs v
|
|
||||||
)))) path cleanAttrs
|
|
||||||
else
|
|
||||||
attrs;
|
|
||||||
|
|
||||||
delayProperties = # implicit attrs argument.
|
|
||||||
let
|
|
||||||
# mapAttrs except that it also recurse into potential mkMerge
|
|
||||||
# functions. This may cause a strictness issue because looking the
|
|
||||||
# type of a string implies evaluating it.
|
|
||||||
iter = fun: path: value:
|
|
||||||
lib.mapAttrs (attr: val:
|
|
||||||
if isProperty val && isMerge val.property then
|
|
||||||
val // { content = map (fun attr) val.content; }
|
|
||||||
else
|
|
||||||
fun attr val
|
|
||||||
) value;
|
|
||||||
in
|
|
||||||
delayPropertiesWithIter iter "";
|
|
||||||
|
|
||||||
# Call onDelay functions.
|
|
||||||
triggerPropertiesDelay = name: attrs:
|
|
||||||
let
|
|
||||||
callOnDelay = p@{property, ...}:
|
|
||||||
if property ? onDelay then
|
|
||||||
property.onDelay name p
|
|
||||||
else
|
|
||||||
p;
|
|
||||||
in
|
|
||||||
foldProperty callOnDelay id attrs;
|
|
||||||
|
|
||||||
# Call onGlobalDelay functions.
|
|
||||||
triggerPropertiesGlobalDelay = name: attrs:
|
|
||||||
let
|
|
||||||
globalDelayFuns = uniqListExt {
|
|
||||||
getter = property: property._type;
|
|
||||||
inputList = foldProperty (p@{property, content, ...}:
|
|
||||||
if property ? onGlobalDelay then
|
|
||||||
[ property ] ++ content
|
|
||||||
else
|
|
||||||
content
|
|
||||||
) (a: []) attrs;
|
|
||||||
};
|
|
||||||
|
|
||||||
callOnGlobalDelay = property: content:
|
|
||||||
property.onGlobalDelay name content;
|
|
||||||
in
|
|
||||||
fold callOnGlobalDelay attrs globalDelayFuns;
|
|
||||||
|
|
||||||
# Expect a list of values which may have properties and return the same
|
|
||||||
# list of values where all properties have been evaluated and where all
|
|
||||||
# ignored values are removed. This trigger property handlers called
|
|
||||||
# `onEval' and `onGlobalEval'.
|
|
||||||
evalProperties = valList:
|
|
||||||
if valList != [] then
|
|
||||||
filter (x: !isNotdef x) (
|
|
||||||
triggerPropertiesGlobalEval (
|
|
||||||
evalLocalProperties valList
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else
|
|
||||||
valList;
|
|
||||||
|
|
||||||
evalLocalProperties = valList:
|
|
||||||
filter (x: !isNotdef x) (
|
|
||||||
map triggerPropertiesEval valList
|
|
||||||
);
|
|
||||||
|
|
||||||
# Call onEval function
|
|
||||||
triggerPropertiesEval = val:
|
|
||||||
foldProperty (p@{property, ...}:
|
|
||||||
if property ? onEval then
|
|
||||||
property.onEval p
|
|
||||||
else
|
|
||||||
p
|
|
||||||
) id val;
|
|
||||||
|
|
||||||
# Call onGlobalEval function
|
|
||||||
triggerPropertiesGlobalEval = valList:
|
|
||||||
let
|
|
||||||
globalEvalFuns = uniqListExt {
|
|
||||||
getter = property: property._type;
|
|
||||||
inputList =
|
|
||||||
fold (attrs: list:
|
|
||||||
foldProperty (p@{property, content, ...}:
|
|
||||||
if property ? onGlobalEval then
|
|
||||||
[ property ] ++ content
|
|
||||||
else
|
|
||||||
content
|
|
||||||
) (a: list) attrs
|
|
||||||
) [] valList;
|
|
||||||
};
|
|
||||||
|
|
||||||
callOnGlobalEval = property: valList: property.onGlobalEval valList;
|
|
||||||
in
|
|
||||||
fold callOnGlobalEval valList globalEvalFuns;
|
|
||||||
|
|
||||||
# Remove all properties on top of a value and return the value.
|
|
||||||
rmProperties =
|
|
||||||
foldProperty (p@{content, ...}: content) id;
|
|
||||||
|
|
||||||
# Copy properties defined on a value on another value.
|
|
||||||
copyProperties = attrs: newAttrs:
|
|
||||||
foldProperty id (x: newAttrs) attrs;
|
|
||||||
|
|
||||||
/* Merge. */
|
|
||||||
|
|
||||||
# Create "merge" statement which is skipped by the delayProperty function
|
|
||||||
# and interpreted by the underlying system using properties (modules).
|
|
||||||
|
|
||||||
# Create a "Merge" property which only contains a condition.
|
|
||||||
isMerge = isType "merge";
|
|
||||||
mkMerge = content: mkProperty {
|
|
||||||
property = {
|
|
||||||
_type = "merge";
|
|
||||||
onDelay = name: val: throw "mkMerge is not the first of the list of properties.";
|
|
||||||
onEval = val: throw "mkMerge is not allowed on option definitions.";
|
|
||||||
};
|
|
||||||
inherit content;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* If. ThenElse. Always. */
|
|
||||||
|
|
||||||
# create "if" statement that can be delayed on sets until a "then-else" or
|
|
||||||
# "always" set is reached. When an always set is reached the condition
|
|
||||||
# is ignore.
|
|
||||||
|
|
||||||
# Create a "If" property which only contains a condition.
|
|
||||||
isIf = isType "if";
|
|
||||||
mkIf = condition: content: mkProperty {
|
|
||||||
property = {
|
|
||||||
_type = "if";
|
|
||||||
onGlobalDelay = onIfGlobalDelay;
|
|
||||||
onEval = onIfEval;
|
|
||||||
inherit condition;
|
|
||||||
};
|
|
||||||
inherit content;
|
|
||||||
};
|
|
||||||
|
|
||||||
mkAssert = assertion: message: content:
|
|
||||||
mkIf
|
|
||||||
(if assertion then true else throw "\nFailed assertion: ${message}")
|
|
||||||
content;
|
|
||||||
|
|
||||||
# Evaluate the "If" statements when either "ThenElse" or "Always"
|
|
||||||
# statement is encountered. Otherwise it removes multiple If statements and
|
|
||||||
# replaces them by one "If" statement where the condition is the list of all
|
|
||||||
# conditions joined with a "and" operation.
|
|
||||||
onIfGlobalDelay = name: content:
|
|
||||||
let
|
|
||||||
# extract if statements and non-if statements and repectively put them
|
|
||||||
# in the attribute list and attrs.
|
|
||||||
ifProps =
|
|
||||||
foldProperty
|
|
||||||
(foldFilter (p: isIf p)
|
|
||||||
# then, push the condition inside the list list
|
|
||||||
(p@{property, content, ...}:
|
|
||||||
{ inherit (content) attrs;
|
|
||||||
list = [property] ++ content.list;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
# otherwise, add the propertie.
|
|
||||||
(p@{property, content, ...}:
|
|
||||||
{ inherit (content) list;
|
|
||||||
attrs = p // { content = content.attrs; };
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(attrs: { list = []; inherit attrs; })
|
|
||||||
content;
|
|
||||||
|
|
||||||
# compute the list of if statements.
|
|
||||||
evalIf = content: condition: list:
|
|
||||||
if list == [] then
|
|
||||||
mkIf condition content
|
|
||||||
else
|
|
||||||
let p = head list; in
|
|
||||||
evalIf content (condition && p.condition) (tail list);
|
|
||||||
in
|
|
||||||
evalIf ifProps.attrs true ifProps.list;
|
|
||||||
|
|
||||||
# Evaluate the condition of the "If" statement to either get the value or
|
|
||||||
# to ignore the value.
|
|
||||||
onIfEval = p@{property, content, ...}:
|
|
||||||
if property.condition then
|
|
||||||
content
|
|
||||||
else
|
|
||||||
mkNotdef;
|
|
||||||
|
|
||||||
/* mkOverride */
|
|
||||||
|
|
||||||
# Create an "Override" statement which allow the user to define
|
|
||||||
# priorities between values. The default priority is 100. The lowest
|
|
||||||
# priorities are kept. The template argument must reproduce the same
|
|
||||||
# attribute set hierarchy to override leaves of the hierarchy.
|
|
||||||
isOverride = isType "override";
|
|
||||||
mkOverrideTemplate = priority: template: content: mkProperty {
|
|
||||||
property = {
|
|
||||||
_type = "override";
|
|
||||||
onDelay = onOverrideDelay;
|
|
||||||
onGlobalEval = onOverrideGlobalEval;
|
|
||||||
inherit priority template;
|
|
||||||
};
|
|
||||||
inherit content;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Like mkOverrideTemplate, but without the template argument.
|
|
||||||
mkOverride = priority: content: mkOverrideTemplate priority {} content;
|
|
||||||
|
|
||||||
# Sugar to override the default value of the option by making a new
|
|
||||||
# default value based on the configuration.
|
|
||||||
mkDefaultValue = mkOverride 1000;
|
|
||||||
mkDefault = mkOverride 1000;
|
|
||||||
mkForce = mkOverride 50;
|
|
||||||
mkStrict = mkOverride 0;
|
|
||||||
|
|
||||||
# Make the template traversal in function of the property traversal. If
|
|
||||||
# the template define a non-empty attribute set, then the property is
|
|
||||||
# copied only on all mentionned attributes inside the template.
|
|
||||||
# Otherwise, the property is kept on all sub-attribute definitions.
|
|
||||||
onOverrideDelay = name: p@{property, content, ...}:
|
|
||||||
let inherit (property) template; in
|
|
||||||
if isAttrs template && template != {} then
|
|
||||||
if hasAttr name template then
|
|
||||||
p // {
|
|
||||||
property = p.property // {
|
|
||||||
template = builtins.getAttr name template;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
# Do not override the attribute \name\
|
|
||||||
else
|
|
||||||
content
|
|
||||||
# Override values defined inside the attribute \name\.
|
|
||||||
else
|
|
||||||
p;
|
|
||||||
|
|
||||||
# Keep values having lowest priority numbers only throwing away those having
|
|
||||||
# a higher priority assigned.
|
|
||||||
onOverrideGlobalEval = valList:
|
|
||||||
let
|
|
||||||
defaultPrio = 100;
|
|
||||||
|
|
||||||
inherit (builtins) lessThan;
|
|
||||||
|
|
||||||
getPrioVal =
|
|
||||||
foldProperty
|
|
||||||
(foldFilter isOverride
|
|
||||||
(p@{property, content, ...}:
|
|
||||||
if content ? priority && lessThan content.priority property.priority then
|
|
||||||
content
|
|
||||||
else
|
|
||||||
content // {
|
|
||||||
inherit (property) priority;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(p@{property, content, ...}:
|
|
||||||
content // {
|
|
||||||
value = p // { content = content.value; };
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) (value: { inherit value; });
|
|
||||||
|
|
||||||
addDefaultPrio = x:
|
|
||||||
if x ? priority then x
|
|
||||||
else x // { priority = defaultPrio; };
|
|
||||||
|
|
||||||
prioValList = map (x: addDefaultPrio (getPrioVal x)) valList;
|
|
||||||
|
|
||||||
higherPrio =
|
|
||||||
if prioValList == [] then
|
|
||||||
defaultPrio
|
|
||||||
else
|
|
||||||
fold (x: min:
|
|
||||||
if lessThan x.priority min then
|
|
||||||
x.priority
|
|
||||||
else
|
|
||||||
min
|
|
||||||
) (head prioValList).priority (tail prioValList);
|
|
||||||
in
|
|
||||||
map (x:
|
|
||||||
if x.priority == higherPrio then
|
|
||||||
x.value
|
|
||||||
else
|
|
||||||
mkNotdef
|
|
||||||
) prioValList;
|
|
||||||
|
|
||||||
/* mkOrder */
|
|
||||||
|
|
||||||
# Order definitions based on there index value. This property is useful
|
|
||||||
# when the result of the merge function depends on the order on the
|
|
||||||
# initial list. (e.g. concatStrings) Definitions are ordered based on
|
|
||||||
# their rank. The lowest ranked definition would be the first to element
|
|
||||||
# of the list used by the merge function. And the highest ranked
|
|
||||||
# definition would be the last. Definitions which does not have any rank
|
|
||||||
# value have the default rank of 100.
|
|
||||||
isOrder = isType "order";
|
|
||||||
mkOrder = rank: content: mkProperty {
|
|
||||||
property = {
|
|
||||||
_type = "order";
|
|
||||||
onGlobalEval = onOrderGlobalEval;
|
|
||||||
inherit rank;
|
|
||||||
};
|
|
||||||
inherit content;
|
|
||||||
};
|
|
||||||
|
|
||||||
mkHeader = mkOrder 10;
|
|
||||||
mkFooter = mkOrder 1000;
|
|
||||||
|
|
||||||
# Fetch the rank of each definition (add the default rank is none) and
|
|
||||||
# sort them based on their ranking.
|
|
||||||
onOrderGlobalEval = valList:
|
|
||||||
let
|
|
||||||
defaultRank = 100;
|
|
||||||
|
|
||||||
inherit (builtins) lessThan;
|
|
||||||
|
|
||||||
getRankVal =
|
|
||||||
foldProperty
|
|
||||||
(foldFilter isOrder
|
|
||||||
(p@{property, content, ...}:
|
|
||||||
if content ? rank then
|
|
||||||
content
|
|
||||||
else
|
|
||||||
content // {
|
|
||||||
inherit (property) rank;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(p@{property, content, ...}:
|
|
||||||
content // {
|
|
||||||
value = p // { content = content.value; };
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) (value: { inherit value; });
|
|
||||||
|
|
||||||
addDefaultRank = x:
|
|
||||||
if x ? rank then x
|
|
||||||
else x // { rank = defaultRank; };
|
|
||||||
|
|
||||||
rankValList = map (x: addDefaultRank (getRankVal x)) valList;
|
|
||||||
|
|
||||||
cmp = x: y:
|
|
||||||
builtins.lessThan x.rank y.rank;
|
|
||||||
in
|
|
||||||
map (x: x.value) (sort cmp rankValList);
|
|
||||||
|
|
||||||
/* mkFixStrictness */
|
|
||||||
|
|
||||||
# This is a hack used to restore laziness on some option definitions.
|
|
||||||
# Some option definitions are evaluated when they are not used. This
|
|
||||||
# error is caused by the strictness of type checking builtins. Builtins
|
|
||||||
# like 'isAttrs' are too strict because they have to evaluate their
|
|
||||||
# arguments to check if the type is correct. This evaluation, cause the
|
|
||||||
# strictness of properties.
|
|
||||||
#
|
|
||||||
# Properties can be stacked on top of each other. The stackability of
|
|
||||||
# properties on top of the option definition is nice for user manipulation
|
|
||||||
# but require to check if the content of the property is not another
|
|
||||||
# property. Such testing implies to verify if this is an attribute set
|
|
||||||
# and if it possess the type 'property'. (see isProperty & typeOf/isType)
|
|
||||||
#
|
|
||||||
# To avoid strict evaluation of option definitions, 'mkFixStrictness' is
|
|
||||||
# introduced. This property protects an option definition by replacing
|
|
||||||
# the base of the stack of properties by 'mkNotDef', when this property is
|
|
||||||
# evaluated it returns the original definition.
|
|
||||||
#
|
|
||||||
# This property is useful over any elements which depends on options which
|
|
||||||
# are raising errors when they get evaluated without the proper settings.
|
|
||||||
#
|
|
||||||
# Plain list and attribute set are lazy structures, which means that the
|
|
||||||
# container gets evaluated but not the content. Thus, using this property
|
|
||||||
# on top of plain list or attribute set is pointless.
|
|
||||||
#
|
|
||||||
# This is a Hack, you should avoid it!
|
|
||||||
|
|
||||||
# This property has a long name because you should avoid it.
|
|
||||||
isFixStrictness = attrs: (typeOf attrs) == "fix-strictness";
|
|
||||||
mkFixStrictness = value:
|
|
||||||
mkProperty {
|
|
||||||
property = {
|
|
||||||
_type = "fix-strictness";
|
|
||||||
onEval = p: value;
|
|
||||||
};
|
|
||||||
content = mkNotdef;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
142
lib/types.nix
142
lib/types.nix
@ -3,10 +3,11 @@
|
|||||||
|
|
||||||
let lib = import ./default.nix; in
|
let lib = import ./default.nix; in
|
||||||
|
|
||||||
with import ./lists.nix;
|
with lib.lists;
|
||||||
with import ./attrsets.nix;
|
with lib.attrsets;
|
||||||
with import ./options.nix;
|
with lib.options;
|
||||||
with import ./trivial.nix;
|
with lib.trivial;
|
||||||
|
with lib.modules;
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
@ -20,48 +21,43 @@ rec {
|
|||||||
|
|
||||||
|
|
||||||
# name (name of the type)
|
# name (name of the type)
|
||||||
# check (check the config value. Before returning false it should trace the bad value eg using traceValIfNot)
|
# check (check the config value)
|
||||||
# merge (default merge function)
|
# merge (default merge function)
|
||||||
# iter (iterate on all elements contained in this type)
|
|
||||||
# fold (fold all elements contained in this type)
|
|
||||||
# hasOptions (boolean: whatever this option contains an option set)
|
|
||||||
# delayOnGlobalEval (boolean: should properties go through the evaluation of this option)
|
|
||||||
# docPath (path concatenated to the option name contained in the option set)
|
# docPath (path concatenated to the option name contained in the option set)
|
||||||
isOptionType = isType "option-type";
|
isOptionType = isType "option-type";
|
||||||
mkOptionType =
|
mkOptionType =
|
||||||
{ name
|
{ name
|
||||||
, check ? (x: true)
|
, check ? (x: true)
|
||||||
, merge ? mergeDefaultOption
|
, merge ? mergeDefaultOption
|
||||||
# Handle complex structure types.
|
, merge' ? args: merge
|
||||||
, iter ? (f: path: v: f path v)
|
|
||||||
, fold ? (op: nul: v: op v nul)
|
|
||||||
, docPath ? lib.id
|
, docPath ? lib.id
|
||||||
# If the type can contains option sets.
|
|
||||||
, hasOptions ? false
|
|
||||||
, delayOnGlobalEval ? false
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
{ _type = "option-type";
|
{ _type = "option-type";
|
||||||
inherit name check merge iter fold docPath hasOptions delayOnGlobalEval;
|
inherit name check merge merge' docPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
types = rec {
|
types = rec {
|
||||||
|
|
||||||
|
unspecified = mkOptionType {
|
||||||
|
name = "unspecified";
|
||||||
|
};
|
||||||
|
|
||||||
bool = mkOptionType {
|
bool = mkOptionType {
|
||||||
name = "boolean";
|
name = "boolean";
|
||||||
check = lib.traceValIfNot builtins.isBool;
|
check = builtins.isBool;
|
||||||
merge = fold lib.or false;
|
merge = fold lib.or false;
|
||||||
};
|
};
|
||||||
|
|
||||||
int = mkOptionType {
|
int = mkOptionType {
|
||||||
name = "integer";
|
name = "integer";
|
||||||
check = lib.traceValIfNot builtins.isInt;
|
check = builtins.isInt;
|
||||||
};
|
};
|
||||||
|
|
||||||
string = mkOptionType {
|
string = mkOptionType {
|
||||||
name = "string";
|
name = "string";
|
||||||
check = lib.traceValIfNot builtins.isString;
|
check = builtins.isString;
|
||||||
merge = lib.concatStrings;
|
merge = lib.concatStrings;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,7 +65,7 @@ rec {
|
|||||||
# configuration file contents.
|
# configuration file contents.
|
||||||
lines = mkOptionType {
|
lines = mkOptionType {
|
||||||
name = "string";
|
name = "string";
|
||||||
check = lib.traceValIfNot builtins.isString;
|
check = builtins.isString;
|
||||||
merge = lib.concatStringsSep "\n";
|
merge = lib.concatStringsSep "\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,48 +77,37 @@ rec {
|
|||||||
|
|
||||||
attrs = mkOptionType {
|
attrs = mkOptionType {
|
||||||
name = "attribute set";
|
name = "attribute set";
|
||||||
check = lib.traceValIfNot isAttrs;
|
check = isAttrs;
|
||||||
merge = fold lib.mergeAttrs {};
|
merge = fold lib.mergeAttrs {};
|
||||||
};
|
};
|
||||||
|
|
||||||
# derivation is a reserved keyword.
|
# derivation is a reserved keyword.
|
||||||
package = mkOptionType {
|
package = mkOptionType {
|
||||||
name = "derivation";
|
name = "derivation";
|
||||||
check = lib.traceValIfNot isDerivation;
|
check = isDerivation;
|
||||||
};
|
};
|
||||||
|
|
||||||
path = mkOptionType {
|
path = mkOptionType {
|
||||||
name = "path";
|
name = "path";
|
||||||
# Hacky: there is no ‘isPath’ primop.
|
# Hacky: there is no ‘isPath’ primop.
|
||||||
check = lib.traceValIfNot (x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/");
|
check = x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/";
|
||||||
};
|
};
|
||||||
|
|
||||||
# drop this in the future:
|
# drop this in the future:
|
||||||
list = builtins.trace "types.list is deprecated, use types.listOf instead" types.listOf;
|
list = builtins.trace "types.list is deprecated; use types.listOf instead" types.listOf;
|
||||||
|
|
||||||
listOf = elemType: mkOptionType {
|
listOf = elemType: mkOptionType {
|
||||||
name = "list of ${elemType.name}s";
|
name = "list of ${elemType.name}s";
|
||||||
check = value: lib.traceValIfNot isList value && all elemType.check value;
|
check = value: isList value && all elemType.check value;
|
||||||
merge = concatLists;
|
merge = defs: map (def: elemType.merge [def]) (concatLists defs);
|
||||||
iter = f: path: list: map (elemType.iter f (path + ".*")) list;
|
|
||||||
fold = op: nul: list: lib.fold (e: l: elemType.fold op l e) nul list;
|
|
||||||
docPath = path: elemType.docPath (path + ".*");
|
docPath = path: elemType.docPath (path + ".*");
|
||||||
inherit (elemType) hasOptions;
|
|
||||||
|
|
||||||
# You cannot define multiple configurations of one entity, therefore
|
|
||||||
# no reason justify to delay properties inside list elements.
|
|
||||||
delayOnGlobalEval = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
attrsOf = elemType: mkOptionType {
|
attrsOf = elemType: mkOptionType {
|
||||||
name = "attribute set of ${elemType.name}s";
|
name = "attribute set of ${elemType.name}s";
|
||||||
check = x: lib.traceValIfNot isAttrs x
|
check = x: isAttrs x && all elemType.check (lib.attrValues x);
|
||||||
&& all elemType.check (lib.attrValues x);
|
merge = lib.zipAttrsWith (name: elemType.merge' { inherit name; });
|
||||||
merge = lib.zipAttrsWith (name: elemType.merge);
|
|
||||||
iter = f: path: set: lib.mapAttrs (name: elemType.iter f (path + "." + name)) set;
|
|
||||||
fold = op: nul: set: fold (e: l: elemType.fold op l e) nul (lib.attrValues set);
|
|
||||||
docPath = path: elemType.docPath (path + ".<name>");
|
docPath = path: elemType.docPath (path + ".<name>");
|
||||||
inherit (elemType) hasOptions delayOnGlobalEval;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# List or attribute set of ...
|
# List or attribute set of ...
|
||||||
@ -143,26 +128,13 @@ rec {
|
|||||||
check = x:
|
check = x:
|
||||||
if isList x then listOnly.check x
|
if isList x then listOnly.check x
|
||||||
else if isAttrs x then attrOnly.check x
|
else if isAttrs x then attrOnly.check x
|
||||||
else lib.traceValIfNot (x: false) x;
|
else false;
|
||||||
## The merge function returns an attribute set
|
merge = defs: attrOnly.merge (imap convertIfList defs);
|
||||||
merge = defs:
|
|
||||||
attrOnly.merge (imap convertIfList defs);
|
|
||||||
iter = f: path: def:
|
|
||||||
if isList def then listOnly.iter f path def
|
|
||||||
else if isAttrs def then attrOnly.iter f path def
|
|
||||||
else throw "Unexpected value";
|
|
||||||
fold = op: nul: def:
|
|
||||||
if isList def then listOnly.fold op nul def
|
|
||||||
else if isAttrs def then attrOnly.fold op nul def
|
|
||||||
else throw "Unexpected value";
|
|
||||||
|
|
||||||
docPath = path: elemType.docPath (path + ".<name?>");
|
docPath = path: elemType.docPath (path + ".<name?>");
|
||||||
inherit (elemType) hasOptions delayOnGlobalEval;
|
};
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
uniq = elemType: mkOptionType {
|
uniq = elemType: mkOptionType {
|
||||||
inherit (elemType) name check iter fold docPath hasOptions;
|
inherit (elemType) name check docPath;
|
||||||
merge = list:
|
merge = list:
|
||||||
if length list == 1 then
|
if length list == 1 then
|
||||||
head list
|
head list
|
||||||
@ -171,54 +143,46 @@ rec {
|
|||||||
};
|
};
|
||||||
|
|
||||||
none = elemType: mkOptionType {
|
none = elemType: mkOptionType {
|
||||||
inherit (elemType) name check iter fold docPath hasOptions;
|
inherit (elemType) name check docPath;
|
||||||
merge = list:
|
merge = list:
|
||||||
throw "No definitions are allowed for this option.";
|
throw "No definitions are allowed for this option.";
|
||||||
};
|
};
|
||||||
|
|
||||||
nullOr = elemType: mkOptionType {
|
nullOr = elemType: mkOptionType {
|
||||||
inherit (elemType) name merge docPath hasOptions;
|
inherit (elemType) docPath;
|
||||||
|
name = "null or ${elemType.name}";
|
||||||
check = x: builtins.isNull x || elemType.check x;
|
check = x: builtins.isNull x || elemType.check x;
|
||||||
iter = f: path: v: if v == null then v else elemType.iter f path v;
|
merge = defs:
|
||||||
fold = op: nul: v: if v == null then nul else elemType.fold op nul v;
|
if all isNull defs then null
|
||||||
|
else if any isNull defs then
|
||||||
|
throw "Some but not all values are null."
|
||||||
|
else elemType.merge defs;
|
||||||
};
|
};
|
||||||
|
|
||||||
functionTo = elemType: mkOptionType {
|
functionTo = elemType: mkOptionType {
|
||||||
name = "function that evaluates to a(n) ${elemType.name}";
|
name = "function that evaluates to a(n) ${elemType.name}";
|
||||||
check = lib.traceValIfNot builtins.isFunction;
|
check = builtins.isFunction;
|
||||||
merge = fns:
|
merge = fns:
|
||||||
args: elemType.merge (map (fn: fn args) fns);
|
args: elemType.merge (map (fn: fn args) fns);
|
||||||
# These are guesses, I don't fully understand iter, fold, delayOnGlobalEval
|
|
||||||
iter = f: path: v:
|
|
||||||
args: elemType.iter f path (v args);
|
|
||||||
fold = op: nul: v:
|
|
||||||
args: elemType.fold op nul (v args);
|
|
||||||
inherit (elemType) delayOnGlobalEval;
|
|
||||||
hasOptions = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# usually used with listOf, attrsOf, loaOf like this:
|
submodule = opts: mkOptionType rec {
|
||||||
# users = mkOption {
|
name = "submodule";
|
||||||
# type = loaOf optionSet;
|
|
||||||
#
|
|
||||||
# # you can omit the list if there is one element only
|
|
||||||
# options = [ {
|
|
||||||
# name = mkOption {
|
|
||||||
# description = "name of the user"
|
|
||||||
# ...
|
|
||||||
# };
|
|
||||||
# # more options here
|
|
||||||
# } { more options } ];
|
|
||||||
# }
|
|
||||||
# TODO: !!! document passing options as an argument to optionSet,
|
|
||||||
# deprecate the current approach.
|
|
||||||
optionSet = mkOptionType {
|
|
||||||
name = "option set";
|
|
||||||
# merge is done in "options.nix > addOptionMakeUp > handleOptionSets"
|
|
||||||
merge = lib.id;
|
|
||||||
check = x: isAttrs x || builtins.isFunction x;
|
check = x: isAttrs x || builtins.isFunction x;
|
||||||
hasOptions = true;
|
# FIXME: make error messages include the parent attrpath.
|
||||||
delayOnGlobalEval = true;
|
merge = merge' {};
|
||||||
|
merge' = args: defs:
|
||||||
|
let
|
||||||
|
coerce = def: if builtins.isFunction def then def else { config = def; };
|
||||||
|
modules = (toList opts) ++ map coerce defs;
|
||||||
|
in (evalModules modules args).config;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Obsolete alternative to configOf. It takes its option
|
||||||
|
# declarations from the ‘options’ attribute of containing option
|
||||||
|
# declaration.
|
||||||
|
optionSet = mkOptionType {
|
||||||
|
name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -19,11 +19,9 @@ rec {
|
|||||||
# Merge the option definitions in all modules, forming the full
|
# Merge the option definitions in all modules, forming the full
|
||||||
# system configuration. It's not checked for undeclared options.
|
# system configuration. It's not checked for undeclared options.
|
||||||
systemModule =
|
systemModule =
|
||||||
pkgs.lib.fixMergeModules configComponents extraArgs;
|
pkgs.lib.evalModules configComponents extraArgs;
|
||||||
|
|
||||||
optionDefinitions = systemModule.config;
|
config = systemModule.config;
|
||||||
optionDeclarations = systemModule.options;
|
|
||||||
inherit (systemModule) options;
|
|
||||||
|
|
||||||
# These are the extra arguments passed to every module. In
|
# These are the extra arguments passed to every module. In
|
||||||
# particular, Nixpkgs is passed through the "pkgs" argument.
|
# particular, Nixpkgs is passed through the "pkgs" argument.
|
||||||
@ -56,16 +54,11 @@ rec {
|
|||||||
# define nixpkgs.config, so it's pointless to evaluate them.
|
# define nixpkgs.config, so it's pointless to evaluate them.
|
||||||
baseModules = [ ../modules/misc/nixpkgs.nix ];
|
baseModules = [ ../modules/misc/nixpkgs.nix ];
|
||||||
pkgs = import ./nixpkgs.nix { system = system_; config = {}; };
|
pkgs = import ./nixpkgs.nix { system = system_; config = {}; };
|
||||||
}).optionDefinitions.nixpkgs;
|
}).config.nixpkgs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit system;
|
inherit system;
|
||||||
inherit (nixpkgsOptions) config;
|
inherit (nixpkgsOptions) config;
|
||||||
});
|
});
|
||||||
|
|
||||||
# Optionally check wether all config values have corresponding
|
|
||||||
# option declarations.
|
|
||||||
config =
|
|
||||||
assert optionDefinitions.environment.checkConfigurationOptions -> pkgs.lib.checkModule "" systemModule;
|
|
||||||
systemModule.config;
|
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,10 @@ in
|
|||||||
type = types.attrsOf (mkOptionType {
|
type = types.attrsOf (mkOptionType {
|
||||||
name = "a string or a list of strings";
|
name = "a string or a list of strings";
|
||||||
merge = xs:
|
merge = xs:
|
||||||
let xs' = evalProperties xs; in
|
if isList (head xs) then concatLists xs
|
||||||
if isList (head xs') then concatLists xs'
|
else if builtins.lessThan 1 (length xs) then abort "variable in ‘environment.variables’ has multiple values"
|
||||||
else if builtins.lessThan 1 (length xs') then abort "variable in ‘environment.variables’ has multiple values"
|
else if !builtins.isString (head xs) then abort "variable in ‘environment.variables’ does not have a string value"
|
||||||
else if !builtins.isString (head xs') then abort "variable in ‘environment.variables’ does not have a string value"
|
else head xs;
|
||||||
else head xs';
|
|
||||||
});
|
});
|
||||||
apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
|
apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
|
||||||
};
|
};
|
||||||
|
@ -52,7 +52,7 @@ in
|
|||||||
idempotent and fast.
|
idempotent and fast.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
merge = mergeTypedOption "script" builtins.isAttrs (fold mergeAttrs {});
|
type = types.attrsOf types.unspecified; # FIXME
|
||||||
|
|
||||||
apply = set: {
|
apply = set: {
|
||||||
script =
|
script =
|
||||||
|
Loading…
Reference in New Issue
Block a user