2009-06-26 15:53:31 +02:00
|
|
|
|
# Definitions related to run-time type checking. Used in particular
|
|
|
|
|
# to type-check NixOS configurations.
|
|
|
|
|
|
|
|
|
|
let lib = import ./default.nix; in
|
|
|
|
|
|
|
|
|
|
with import ./lists.nix;
|
|
|
|
|
with import ./attrsets.nix;
|
|
|
|
|
with import ./options.nix;
|
2011-06-14 04:41:13 +02:00
|
|
|
|
with import ./trivial.nix;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
|
|
|
|
|
rec {
|
|
|
|
|
|
|
|
|
|
hasType = x: isAttrs x && x ? _type;
|
2012-12-31 19:59:30 +01:00
|
|
|
|
typeOf = x: x._type or "";
|
2009-06-26 15:53:31 +02:00
|
|
|
|
|
2009-11-19 18:19:39 +01:00
|
|
|
|
setType = typeName: value: value // {
|
|
|
|
|
_type = typeName;
|
|
|
|
|
};
|
|
|
|
|
|
2011-06-14 04:41:13 +02:00
|
|
|
|
|
2009-06-26 15:53:31 +02:00
|
|
|
|
# name (name of the type)
|
|
|
|
|
# check (boolean 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)
|
2009-10-12 15:37:00 +02:00
|
|
|
|
# delayOnGlobalEval (boolean: should properties go through the evaluation of this option)
|
2009-10-10 01:03:24 +02:00
|
|
|
|
# docPath (path concatenated to the option name contained in the option set)
|
2009-06-26 15:53:31 +02:00
|
|
|
|
isOptionType = attrs: typeOf attrs == "option-type";
|
|
|
|
|
mkOptionType =
|
|
|
|
|
{ name
|
|
|
|
|
, check ? (x: true)
|
|
|
|
|
, merge ? mergeDefaultOption
|
|
|
|
|
# Handle complex structure types.
|
|
|
|
|
, iter ? (f: path: v: f path v)
|
|
|
|
|
, fold ? (op: nul: v: op v nul)
|
|
|
|
|
, docPath ? lib.id
|
|
|
|
|
# If the type can contains option sets.
|
|
|
|
|
, hasOptions ? false
|
2009-10-12 15:37:00 +02:00
|
|
|
|
, delayOnGlobalEval ? false
|
2009-06-26 15:53:31 +02:00
|
|
|
|
}:
|
|
|
|
|
|
|
|
|
|
{ _type = "option-type";
|
2009-10-12 15:37:00 +02:00
|
|
|
|
inherit name check merge iter fold docPath hasOptions delayOnGlobalEval;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
};
|
|
|
|
|
|
2011-06-14 04:41:13 +02:00
|
|
|
|
|
2009-11-07 02:58:56 +01:00
|
|
|
|
types = rec {
|
2009-06-26 15:53:31 +02:00
|
|
|
|
|
|
|
|
|
bool = mkOptionType {
|
|
|
|
|
name = "boolean";
|
|
|
|
|
check = lib.traceValIfNot builtins.isBool;
|
|
|
|
|
merge = fold lib.or false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int = mkOptionType {
|
|
|
|
|
name = "integer";
|
|
|
|
|
check = lib.traceValIfNot builtins.isInt;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
string = mkOptionType {
|
|
|
|
|
name = "string";
|
2012-05-25 19:19:07 +02:00
|
|
|
|
check = lib.traceValIfNot builtins.isString;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
merge = lib.concatStrings;
|
|
|
|
|
};
|
|
|
|
|
|
2013-02-11 15:28:41 +01:00
|
|
|
|
# Like ‘string’, but add newlines between every value. Useful for
|
|
|
|
|
# configuration file contents.
|
|
|
|
|
lines = mkOptionType {
|
|
|
|
|
name = "string";
|
|
|
|
|
check = lib.traceValIfNot builtins.isString;
|
|
|
|
|
merge = lib.concatStringsSep "\n";
|
|
|
|
|
};
|
|
|
|
|
|
2009-11-07 02:58:56 +01:00
|
|
|
|
envVar = mkOptionType {
|
|
|
|
|
name = "environment variable";
|
|
|
|
|
inherit (string) check;
|
|
|
|
|
merge = lib.concatStringsSep ":";
|
|
|
|
|
};
|
|
|
|
|
|
2009-06-26 15:53:31 +02:00
|
|
|
|
attrs = mkOptionType {
|
|
|
|
|
name = "attribute set";
|
2011-06-14 04:41:13 +02:00
|
|
|
|
check = lib.traceValIfNot isAttrs;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
merge = fold lib.mergeAttrs {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# derivation is a reserved keyword.
|
|
|
|
|
package = mkOptionType {
|
|
|
|
|
name = "derivation";
|
|
|
|
|
check = lib.traceValIfNot isDerivation;
|
|
|
|
|
};
|
|
|
|
|
|
2012-05-25 19:19:07 +02:00
|
|
|
|
path = mkOptionType {
|
|
|
|
|
name = "path";
|
|
|
|
|
# Hacky: there is no ‘isPath’ primop.
|
2012-07-12 17:53:02 +02:00
|
|
|
|
check = lib.traceValIfNot (x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/");
|
2012-05-25 19:19:07 +02:00
|
|
|
|
};
|
|
|
|
|
|
2009-10-10 01:03:24 +02:00
|
|
|
|
listOf = types.list;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
list = elemType: mkOptionType {
|
|
|
|
|
name = "list of ${elemType.name}s";
|
|
|
|
|
check = value: lib.traceValIfNot isList value && all elemType.check value;
|
|
|
|
|
merge = concatLists;
|
|
|
|
|
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 + ".*");
|
|
|
|
|
inherit (elemType) hasOptions;
|
2009-10-10 01:03:24 +02:00
|
|
|
|
|
|
|
|
|
# You cannot define multiple configurations of one entity, therefore
|
|
|
|
|
# no reason justify to delay properties inside list elements.
|
2009-10-12 15:37:00 +02:00
|
|
|
|
delayOnGlobalEval = false;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
attrsOf = elemType: mkOptionType {
|
2011-04-27 20:41:27 +02:00
|
|
|
|
name = "attribute set of ${elemType.name}s";
|
2011-06-14 04:41:13 +02:00
|
|
|
|
check = x: lib.traceValIfNot isAttrs x
|
2009-06-26 15:53:31 +02:00
|
|
|
|
&& fold (e: v: v && elemType.check e) true (lib.attrValues x);
|
2009-10-05 20:10:42 +02:00
|
|
|
|
merge = lib.zip (name: elemType.merge);
|
2009-06-26 15:53:31 +02:00
|
|
|
|
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>");
|
2009-10-12 15:37:00 +02:00
|
|
|
|
inherit (elemType) hasOptions delayOnGlobalEval;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
};
|
|
|
|
|
|
2011-06-14 04:41:13 +02:00
|
|
|
|
# List or attribute set of ...
|
|
|
|
|
loaOf = elemType:
|
|
|
|
|
let
|
|
|
|
|
convertIfList = defIdx: def:
|
|
|
|
|
if isList def then
|
|
|
|
|
listToAttrs (
|
|
|
|
|
flip imap def (elemIdx: elem:
|
|
|
|
|
nameValuePair "unnamed-${toString defIdx}.${toString elemIdx}" elem))
|
|
|
|
|
else
|
|
|
|
|
def;
|
|
|
|
|
listOnly = listOf elemType;
|
|
|
|
|
attrOnly = attrsOf elemType;
|
|
|
|
|
|
|
|
|
|
in mkOptionType {
|
|
|
|
|
name = "list or attribute set of ${elemType.name}s";
|
|
|
|
|
check = x:
|
|
|
|
|
if isList x then listOnly.check x
|
|
|
|
|
else if isAttrs x then attrOnly.check x
|
|
|
|
|
else lib.traceValIfNot (x: false) x;
|
|
|
|
|
## The merge function returns an attribute set
|
|
|
|
|
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?>");
|
|
|
|
|
inherit (elemType) hasOptions delayOnGlobalEval;
|
|
|
|
|
}
|
|
|
|
|
;
|
|
|
|
|
|
2009-06-26 15:53:31 +02:00
|
|
|
|
uniq = elemType: mkOptionType {
|
|
|
|
|
inherit (elemType) name check iter fold docPath hasOptions;
|
|
|
|
|
merge = list:
|
2012-08-13 20:19:31 +02:00
|
|
|
|
if length list == 1 then
|
2009-06-26 15:53:31 +02:00
|
|
|
|
head list
|
|
|
|
|
else
|
|
|
|
|
throw "Multiple definitions. Only one is allowed for this option.";
|
|
|
|
|
};
|
|
|
|
|
|
2009-11-05 16:39:45 +01:00
|
|
|
|
none = elemType: mkOptionType {
|
|
|
|
|
inherit (elemType) name check iter fold docPath hasOptions;
|
|
|
|
|
merge = list:
|
|
|
|
|
throw "No definitions are allowed for this option.";
|
|
|
|
|
};
|
|
|
|
|
|
2009-06-26 15:53:31 +02:00
|
|
|
|
nullOr = elemType: mkOptionType {
|
|
|
|
|
inherit (elemType) name merge docPath hasOptions;
|
|
|
|
|
check = x: builtins.isNull x || elemType.check x;
|
|
|
|
|
iter = f: path: v: if v == null then v else elemType.iter f path v;
|
|
|
|
|
fold = op: nul: v: if v == null then nul else elemType.fold op nul v;
|
|
|
|
|
};
|
|
|
|
|
|
2012-12-20 04:49:21 +01:00
|
|
|
|
functionTo = elemType: mkOptionType {
|
|
|
|
|
name = "function that evaluates to a(n) ${elemType.name}";
|
|
|
|
|
check = lib.traceValIfNot builtins.isFunction;
|
|
|
|
|
merge = fns:
|
2012-12-20 06:52:51 +01:00
|
|
|
|
args: elemType.merge (map (fn: fn args) fns);
|
2012-12-20 04:49:21 +01:00
|
|
|
|
# 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);
|
2012-12-20 06:28:12 +01:00
|
|
|
|
inherit (elemType) delayOnGlobalEval;
|
|
|
|
|
hasOptions = false;
|
2012-12-20 04:49:21 +01:00
|
|
|
|
};
|
|
|
|
|
|
2009-07-17 14:04:34 +02:00
|
|
|
|
# !!! this should be a type constructor that takes the options as
|
|
|
|
|
# an argument.
|
2009-06-26 15:53:31 +02:00
|
|
|
|
optionSet = mkOptionType {
|
|
|
|
|
name = "option set";
|
2009-10-05 20:10:42 +02:00
|
|
|
|
# merge is done in "options.nix > addOptionMakeUp > handleOptionSets"
|
|
|
|
|
merge = lib.id;
|
2009-11-07 13:15:39 +01:00
|
|
|
|
check = x: isAttrs x || builtins.isFunction x;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
hasOptions = true;
|
2009-10-12 15:37:00 +02:00
|
|
|
|
delayOnGlobalEval = true;
|
2009-06-26 15:53:31 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|