forked from mirrors/nixpkgs
7cd6b3cf76
The change caused lots of evaluation errors, since it caused derivations to no longer be valid paths as well.
241 lines
7.8 KiB
Nix
241 lines
7.8 KiB
Nix
# Definitions related to run-time type checking. Used in particular
|
||
# to type-check NixOS configurations.
|
||
|
||
with import ./lists.nix;
|
||
with import ./attrsets.nix;
|
||
with import ./options.nix;
|
||
with import ./trivial.nix;
|
||
with import ./strings.nix;
|
||
with {inherit (import ./modules.nix) mergeDefinitions; };
|
||
|
||
rec {
|
||
|
||
isType = type: x: (x._type or "") == type;
|
||
|
||
setType = typeName: value: value // {
|
||
_type = typeName;
|
||
};
|
||
|
||
|
||
isOptionType = isType "option-type";
|
||
mkOptionType =
|
||
{ # Human-readable representation of the type.
|
||
name
|
||
, # Function applied to each definition that should return true if
|
||
# its type-correct, false otherwise.
|
||
check ? (x: true)
|
||
, # Merge a list of definitions together into a single value.
|
||
# This function is called with two arguments: the location of
|
||
# the option in the configuration as a list of strings
|
||
# (e.g. ["boot" "loader "grub" "enable"]), and a list of
|
||
# definition values and locations (e.g. [ { file = "/foo.nix";
|
||
# value = 1; } { file = "/bar.nix"; value = 2 } ]).
|
||
merge ? mergeDefaultOption
|
||
, # Return a flat list of sub-options. Used to generate
|
||
# documentation.
|
||
getSubOptions ? prefix: {}
|
||
, # List of modules if any, or null if none.
|
||
getSubModules ? null
|
||
, # Function for building the same option type with a different list of
|
||
# modules.
|
||
substSubModules ? m: null
|
||
}:
|
||
{ _type = "option-type";
|
||
inherit name check merge getSubOptions getSubModules substSubModules;
|
||
};
|
||
|
||
|
||
types = rec {
|
||
|
||
unspecified = mkOptionType {
|
||
name = "unspecified";
|
||
};
|
||
|
||
bool = mkOptionType {
|
||
name = "boolean";
|
||
check = isBool;
|
||
merge = mergeEqualOption;
|
||
};
|
||
|
||
int = mkOptionType {
|
||
name = "integer";
|
||
check = isInt;
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
str = mkOptionType {
|
||
name = "string";
|
||
check = isString;
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
# Merge multiple definitions by concatenating them (with the given
|
||
# separator between the values).
|
||
separatedString = sep: mkOptionType {
|
||
name = "string";
|
||
check = isString;
|
||
merge = loc: defs: concatStringsSep sep (getValues defs);
|
||
};
|
||
|
||
lines = separatedString "\n";
|
||
commas = separatedString ",";
|
||
envVar = separatedString ":";
|
||
|
||
# Deprecated; should not be used because it quietly concatenates
|
||
# strings, which is usually not what you want.
|
||
string = separatedString "";
|
||
|
||
attrs = mkOptionType {
|
||
name = "attribute set";
|
||
check = isAttrs;
|
||
merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
|
||
};
|
||
|
||
# derivation is a reserved keyword.
|
||
package = mkOptionType {
|
||
name = "derivation";
|
||
check = x: isDerivation x || isStorePath x;
|
||
merge = loc: defs:
|
||
let res = mergeOneOption loc defs;
|
||
in if isDerivation res then res else toDerivation res;
|
||
};
|
||
|
||
path = mkOptionType {
|
||
name = "path";
|
||
# Hacky: there is no ‘isPath’ primop.
|
||
check = x: builtins.substring 0 1 (toString x) == "/";
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
# drop this in the future:
|
||
list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
|
||
|
||
listOf = elemType: mkOptionType {
|
||
name = "list of ${elemType.name}s";
|
||
check = isList;
|
||
merge = loc: defs:
|
||
map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def: imap (m: def':
|
||
(mergeDefinitions
|
||
(loc ++ ["[definition ${toString n}-entry ${toString m}]"])
|
||
elemType
|
||
[{ inherit (def) file; value = def'; }]
|
||
).optionalValue
|
||
) def.value) defs)));
|
||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||
getSubModules = elemType.getSubModules;
|
||
substSubModules = m: listOf (elemType.substSubModules m);
|
||
};
|
||
|
||
attrsOf = elemType: mkOptionType {
|
||
name = "attribute set of ${elemType.name}s";
|
||
check = isAttrs;
|
||
merge = loc: defs:
|
||
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
|
||
(mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
|
||
)
|
||
# Push down position info.
|
||
(map (def: listToAttrs (mapAttrsToList (n: def':
|
||
{ name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs)));
|
||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
||
getSubModules = elemType.getSubModules;
|
||
substSubModules = m: attrsOf (elemType.substSubModules m);
|
||
};
|
||
|
||
# List or attribute set of ...
|
||
loaOf = elemType:
|
||
let
|
||
convertIfList = defIdx: def:
|
||
if isList def.value then
|
||
{ inherit (def) file;
|
||
value = listToAttrs (
|
||
imap (elemIdx: elem:
|
||
{ name = elem.name or "unnamed-${toString defIdx}.${toString elemIdx}";
|
||
value = elem;
|
||
}) def.value);
|
||
}
|
||
else
|
||
def;
|
||
listOnly = listOf elemType;
|
||
attrOnly = attrsOf elemType;
|
||
in mkOptionType {
|
||
name = "list or attribute set of ${elemType.name}s";
|
||
check = x: isList x || isAttrs x;
|
||
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
||
getSubModules = elemType.getSubModules;
|
||
substSubModules = m: loaOf (elemType.substSubModules m);
|
||
};
|
||
|
||
uniq = elemType: mkOptionType {
|
||
inherit (elemType) name check;
|
||
merge = mergeOneOption;
|
||
getSubOptions = elemType.getSubOptions;
|
||
getSubModules = elemType.getSubModules;
|
||
substSubModules = m: uniq (elemType.substSubModules m);
|
||
};
|
||
|
||
nullOr = elemType: mkOptionType {
|
||
name = "null or ${elemType.name}";
|
||
check = x: builtins.isNull x || elemType.check x;
|
||
merge = loc: defs:
|
||
let nrNulls = count (def: isNull def.value) defs; in
|
||
if nrNulls == length defs then null
|
||
else if nrNulls != 0 then
|
||
throw "The option `${showOption loc}' is defined both null and not null, in ${showFiles (getFiles defs)}."
|
||
else elemType.merge loc defs;
|
||
getSubOptions = elemType.getSubOptions;
|
||
getSubModules = elemType.getSubModules;
|
||
substSubModules = m: nullOr (elemType.substSubModules m);
|
||
};
|
||
|
||
submodule = opts:
|
||
let
|
||
opts' = toList opts;
|
||
inherit (import ./modules.nix) evalModules;
|
||
in
|
||
mkOptionType rec {
|
||
name = "submodule";
|
||
check = x: isAttrs x || isFunction x;
|
||
merge = loc: defs:
|
||
let
|
||
coerce = def: if isFunction def then def else { config = def; };
|
||
modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
|
||
in (evalModules {
|
||
inherit modules;
|
||
args.name = last loc;
|
||
prefix = loc;
|
||
}).config;
|
||
getSubOptions = prefix: (evalModules
|
||
{ modules = opts'; inherit prefix;
|
||
# FIXME: hack to get shit to evaluate.
|
||
args = { name = ""; }; }).options;
|
||
getSubModules = opts';
|
||
substSubModules = m: submodule m;
|
||
};
|
||
|
||
enum = values: mkOptionType {
|
||
name = "one of ${concatStringsSep ", " values}";
|
||
check = flip elem values;
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
either = t1: t2: mkOptionType {
|
||
name = "${t1.name} or ${t2.name}";
|
||
check = x: t1.check x || t2.check x;
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
# 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";
|
||
};
|
||
|
||
# Augment the given type with an additional type check function.
|
||
addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
|
||
|
||
};
|
||
|
||
}
|