nixpkgs-suyu/lib/types.nix
Eelco Dolstra 0e333688ce 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.
2013-10-28 22:45:55 +01:00

190 lines
5.2 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Definitions related to run-time type checking. Used in particular
# to type-check NixOS configurations.
let lib = import ./default.nix; in
with lib.lists;
with lib.attrsets;
with lib.options;
with lib.trivial;
with lib.modules;
rec {
isType = type: x: (x._type or "") == type;
hasType = x: isAttrs x && x ? _type;
typeOf = x: x._type or "";
setType = typeName: value: value // {
_type = typeName;
};
# name (name of the type)
# check (check the config value)
# merge (default merge function)
# docPath (path concatenated to the option name contained in the option set)
isOptionType = isType "option-type";
mkOptionType =
{ name
, check ? (x: true)
, merge ? mergeDefaultOption
, merge' ? args: merge
, docPath ? lib.id
}:
{ _type = "option-type";
inherit name check merge merge' docPath;
};
types = rec {
unspecified = mkOptionType {
name = "unspecified";
};
bool = mkOptionType {
name = "boolean";
check = builtins.isBool;
merge = fold lib.or false;
};
int = mkOptionType {
name = "integer";
check = builtins.isInt;
};
string = mkOptionType {
name = "string";
check = builtins.isString;
merge = lib.concatStrings;
};
# Like string, but add newlines between every value. Useful for
# configuration file contents.
lines = mkOptionType {
name = "string";
check = builtins.isString;
merge = lib.concatStringsSep "\n";
};
envVar = mkOptionType {
name = "environment variable";
inherit (string) check;
merge = lib.concatStringsSep ":";
};
attrs = mkOptionType {
name = "attribute set";
check = isAttrs;
merge = fold lib.mergeAttrs {};
};
# derivation is a reserved keyword.
package = mkOptionType {
name = "derivation";
check = isDerivation;
};
path = mkOptionType {
name = "path";
# Hacky: there is no isPath primop.
check = x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/";
};
# 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 = value: isList value && all elemType.check value;
merge = defs: map (def: elemType.merge [def]) (concatLists defs);
docPath = path: elemType.docPath (path + ".*");
};
attrsOf = elemType: mkOptionType {
name = "attribute set of ${elemType.name}s";
check = x: isAttrs x && all elemType.check (lib.attrValues x);
merge = lib.zipAttrsWith (name: elemType.merge' { inherit name; });
docPath = path: elemType.docPath (path + ".<name>");
};
# 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 false;
merge = defs: attrOnly.merge (imap convertIfList defs);
docPath = path: elemType.docPath (path + ".<name?>");
};
uniq = elemType: mkOptionType {
inherit (elemType) name check docPath;
merge = list:
if length list == 1 then
head list
else
throw "Multiple definitions of ${elemType.name}. Only one is allowed for this option.";
};
none = elemType: mkOptionType {
inherit (elemType) name check docPath;
merge = list:
throw "No definitions are allowed for this option.";
};
nullOr = elemType: mkOptionType {
inherit (elemType) docPath;
name = "null or ${elemType.name}";
check = x: builtins.isNull x || elemType.check x;
merge = defs:
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 {
name = "function that evaluates to a(n) ${elemType.name}";
check = builtins.isFunction;
merge = fns:
args: elemType.merge (map (fn: fn args) fns);
};
submodule = opts: mkOptionType rec {
name = "submodule";
check = x: isAttrs x || builtins.isFunction x;
# FIXME: make error messages include the parent attrpath.
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";
};
};
}