Revert "Add the tool "nixos-typecheck" that can check an option declaration to:"

This reverts commit cad8957eab. It
breaks NixOps, but more importantly, such major changes to the module
system really need to be reviewed.
nixos-19.03
Eelco Dolstra 7 years ago
parent bf4cafd1dd
commit f3d94cfc23
  1. 99
      lib/modules.nix
  2. 145
      lib/options.nix
  3. 217
      lib/types.nix
  4. 2
      nixos/default.nix
  5. 6
      nixos/doc/manual/default.nix
  6. 106
      nixos/doc/manual/development/option-declarations.xml
  7. 15
      nixos/lib/eval-config.nix
  8. 91
      nixos/lib/typechecker.nix
  9. 1
      nixos/modules/config/i18n.nix
  10. 1
      nixos/modules/config/sysctl.nix
  11. 4
      nixos/modules/installer/tools/nixos-option.sh
  12. 52
      nixos/modules/installer/tools/nixos-typecheck.sh
  13. 8
      nixos/modules/installer/tools/tools.nix
  14. 1
      nixos/modules/misc/meta.nix
  15. 49
      nixos/modules/misc/nixpkgs.nix
  16. 1
      nixos/modules/module-list.nix
  17. 3
      nixos/modules/services/misc/gitlab.nix
  18. 4
      nixos/modules/services/misc/ihaskell.nix
  19. 5
      nixos/modules/services/misc/nixos-manual.nix
  20. 3
      nixos/modules/services/web-servers/apache-httpd/default.nix
  21. 3
      nixos/modules/services/x11/window-managers/xmonad.nix
  22. 1
      nixos/modules/system/boot/kernel.nix
  23. 5
      nixos/modules/system/boot/systemd-unit-options.nix
  24. 2
      nixos/release.nix

@ -23,6 +23,8 @@ rec {
specialArgs ? {}
, # This would be remove in the future, Prefer _module.args option instead.
args ? {}
, # This would be remove in the future, Prefer _module.check option instead.
check ? true
}:
let
# This internal module declare internal options under the `_module'
@ -43,22 +45,9 @@ rec {
_module.check = mkOption {
type = types.bool;
internal = true;
default = true;
default = check;
description = "Whether to check whether all option definitions have matching declarations.";
};
_module.typeInference = mkOption {
type = types.nullOr types.str;
internal = true;
default = null; # TODO: Move away from 'null' after enough testing.
description = ''
Mode of type inferencing. Possible values are:
null = Disable type inferencing completely. Use 'types.unspecified' for every option without type definition.
"silent" = Try to infer type of option without type definition, but do not print anything.
"printUnspecified" = Try to infer type of option without type definition and print options for which no full type could be inferred.
"printAll" = Try to infer type of option without type definition and print all options without type definition.
'';
};
};
config = {
@ -71,7 +60,7 @@ rec {
# Note: the list of modules is reversed to maintain backward
# compatibility with the old module system. Not sure if this is
# the most sensible policy.
options = mergeModules (config._module) prefix (reverseList closed);
options = mergeModules prefix (reverseList closed);
# Traverse options and extract the option values into the final
# config set. At the same time, check whether all option
@ -181,11 +170,11 @@ rec {
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 = _module: prefix: modules:
mergeModules' _module prefix modules
mergeModules = prefix: modules:
mergeModules' prefix modules
(concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
mergeModules' = _module: prefix: options: configs:
mergeModules' = prefix: options: configs:
listToAttrs (map (name: {
# We're descending into attribute ‘name’.
inherit name;
@ -211,8 +200,8 @@ rec {
(filter (m: m.config ? ${name}) configs);
in
if nrOptions == length decls then
let opt = fixupOptionType _module.typeInference loc (mergeOptionDecls loc decls);
in evalOptionValue _module loc opt defns'
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
in evalOptionValue loc opt defns'
else if nrOptions != 0 then
let
firstOption = findFirst (m: isOption m.options) "" decls;
@ -220,7 +209,7 @@ rec {
in
throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
else
mergeModules' _module loc decls defns;
mergeModules' loc decls defns;
}) (concatMap (m: attrNames m.options) options))
// { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; };
@ -269,7 +258,7 @@ rec {
/* Merge all the definitions of an option to produce the final
config value. */
evalOptionValue = _module: loc: opt: defs:
evalOptionValue = loc: opt: defs:
let
# Add in the default value for this option, if any.
defs' =
@ -281,7 +270,7 @@ rec {
if opt.readOnly or false && length defs' > 1 then
throw "The option `${showOption loc}' is read-only, but it's set multiple times."
else
mergeDefinitions _module loc opt.type defs';
mergeDefinitions loc opt.type defs';
# Check whether the option is defined, and apply the ‘apply’
# function to the merged value. This allows options to yield a
@ -302,7 +291,7 @@ rec {
};
# Merge definitions of a value of a given type.
mergeDefinitions = _module: loc: type: defs: rec {
mergeDefinitions = loc: type: defs: rec {
defsFinal =
let
# Process mkMerge and mkIf properties.
@ -325,7 +314,7 @@ rec {
mergedValue = foldl' (res: def:
if type.check def.value then res
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.")
(type.merge _module loc defsFinal) defsFinal;
(type.merge loc defsFinal) defsFinal;
isDefined = defsFinal != [];
@ -423,7 +412,7 @@ rec {
/* Hack for backward compatibility: convert options of type
optionSet to options of type submodule. FIXME: remove
eventually. */
fixupOptionType = typeInference: loc: opt:
fixupOptionType = loc: opt:
let
options = opt.options or
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
@ -435,66 +424,12 @@ rec {
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
if opt.type.getSubModules or null == null
then opt // { type = f (opt.type or (inferType typeInference loc opt)); }
then opt // { type = f (opt.type or types.unspecified); }
else opt // { type = opt.type.substSubModules opt.options; options = []; };
/* Function that tries to infer the type of an option from the default value of the option. */
inferType = mode: loc: opt:
let
doc = x: elemAt x 0;
type = x: elemAt x 1;
containsUnspecified = x: elemAt x 2;
inferType' = def:
if isDerivation def then [ "package" types.package false ]
else if isBool def then [ "bool" types.bool false ]
else if builtins.isString def then [ "str" types.str false ]
else if isInt def then [ "int" types.int false ]
else if isFunction def then [ "functionTo unspecified" (types.functionTo types.unspecified) true ]
else if isList def then
let nestedType = if (length def > 0) && (all (x: (type (inferType' x)) == (type (inferType' (head def)))) def)
then inferType' (head def)
else [ "unspecified" types.unspecified true ];
in [ "listOf ${doc nestedType}" (types.listOf (type nestedType)) (containsUnspecified nestedType) ]
else if isAttrs def then
let list = mapAttrsToList (_: v: v) (removeAttrs def ["_args"]);
nestedType = if (length list > 0) && (all (x: (type (inferType' x)) == (type (inferType' (head list)))) list)
then inferType' (head list)
else [ "unspecified" types.unspecified true ];
in [ "attrsOf ${doc nestedType}" (types.attrsOf (type nestedType)) (containsUnspecified nestedType) ]
else [ "unspecified" types.unspecified true ];
inferDoc = x: ''
Inferring the type of "${showOption loc}" to "${doc x}".
Please verify the inferred type and define the type explicitely in ${showFiles opt.declarations}!
'';
inferredType = printMode:
let inferred = inferType' opt.default;
in if printMode == "silent" then type inferred
else if printMode == "printAll" then builtins.trace (inferDoc inferred) (type inferred)
else if printMode == "printUnspecified" && (containsUnspecified inferred) then builtins.trace (inferDoc inferred) (type inferred)
else type inferred;
noInferDoc = ''
Could not infer a type for "${showOption loc}", using "unspecified" instead.
Please define the type explicitely in ${showFiles opt.declarations}!
'';
hasDefault = (opt ? default) && !(opt ? defaultText);
isExternalVisible = (opt.visible or true) && !(opt.internal or false);
in
if isNull mode || !isExternalVisible
then types.unspecified
else if hasDefault
then inferredType mode /* Set to 'true' to see every type that is being inferred, not just those types that result in 'unspecified'. */
else if mode != "silent" then builtins.trace noInferDoc types.unspecified else types.unspecified;
/* Properties. */
mkIf = condition: content:
@ -562,7 +497,7 @@ rec {
/* Compatibility. */
fixMergeModules = modules: args: evalModules { inherit args; modules = (modules ++ [{ _module.check = false; }]); };
fixMergeModules = modules: args: evalModules { inherit modules args; check = false; };
/* Return a module that causes a warning to be shown if the

@ -6,7 +6,6 @@ with import ./trivial.nix;
with import ./lists.nix;
with import ./attrsets.nix;
with import ./strings.nix;
with {inherit (import ./types.nix) types; };
rec {
@ -43,17 +42,16 @@ rec {
description = "Sink for option definitions.";
type = mkOptionType {
name = "sink";
typerep = "(sink)";
check = x: true;
merge = config: loc: defs: false;
merge = loc: defs: false;
};
apply = x: throw "Option value is not readable because the option is not declared.";
} // attrs);
mergeDefaultOption = config: loc: defs:
mergeDefaultOption = loc: defs:
let list = getValues defs; in
if length list == 1 then head list
else if all isFunction list then x: mergeDefaultOption config loc (map (f: f x) list)
else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list)
else if all isList list then concatLists list
else if all isAttrs list then foldl' lib.mergeAttrs {} list
else if all isBool list then foldl' lib.or false list
@ -61,14 +59,14 @@ rec {
else if all isInt list && all (x: x == head list) list then head list
else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
mergeOneOption = config: loc: defs:
mergeOneOption = loc: defs:
if defs == [] then abort "This case should never happen."
else if length defs != 1 then
throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}."
else (head defs).value;
/* "Merge" option definitions by checking that they all have the same value. */
mergeEqualOption = config: loc: defs:
mergeEqualOption = loc: defs:
if defs == [] then abort "This case should never happen."
else foldl' (val: def:
if def.value != val then
@ -79,154 +77,53 @@ rec {
getValues = map (x: x.value);
getFiles = map (x: x.file);
# Generate documentation template from the list of option declaration like
# the set generated with filterOptionSets.
optionAttrSetToDocList = optionAttrSetToDocList' [];
optionAttrSetToDocList' = prefix: internalModuleConfig: options:
optionAttrSetToDocList' = prefix: options:
concatMap (opt:
let
decls = filter (x: x != unknownModule) opt.declarations;
docOption = rec {
name = showOption opt.loc;
description = opt.description or (throw "Option `${name}' has no description.");
declarations = decls;
declarations = filter (x: x != unknownModule) opt.declarations;
internal = opt.internal or false;
visible = opt.visible or true;
readOnly = opt.readOnly or false;
type = opt.type.name or null;
}
// (if opt ? example then { example = detectDerivation decls opt.example; } else {})
// (if opt ? default then { default = detectDerivation decls opt.default; } else {})
// (if opt ? example then { example = scrubOptionValue opt.example; } else {})
// (if opt ? default then { default = scrubOptionValue opt.default; } else {})
// (if opt ? defaultText then { default = opt.defaultText; } else {});
subOptions =
let ss = opt.type.getSubOptionsPrefixed opt.loc;
in if ss != {} then optionAttrSetToDocList' opt.loc internalModuleConfig (ss internalModuleConfig) else [];
let ss = opt.type.getSubOptions opt.loc;
in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
in
[ docOption ] ++ subOptions) (collect isOption options);
# TODO: Use "extractOptionAttrSet" instead of "optionAttrSetToDocList'" to reduce the code size.
# It should be a drop-in-replacement. But first, examine the impact on the evaluation time.
# optionAttrSetToDocList = extractOptionAttrSet true [];
# Generate a machine readable specification of the list of option declarations.
optionAttrSetToParseableSpecifications = extractOptionAttrSet false [];
extractOptionAttrSet = toDoc: prefix: internalModuleConfig: options:
concatMap (opt:
let
optionName = showOption opt.loc;
# Check if a type contains derivations, that is check if a type nests
# a 'package', 'packageSet' or 'nixpkgsConfig' type.
hasDerivation = any (t: elem t opt.type.nestedTypes) ((map (x: x.typerep) (with types; [package packageSet])) ++ ["(nixpkgsConfig)"]);
# Check if type is 'path' which can potentially contain a derivation.
maybeHiddenDerivation = any (t: elem t opt.type.nestedTypes) (map (x: x.typerep) (with types; [path]));
isDefaultValue = elem opt.default opt.type.defaultValues;
/* Enforce that the example attribute is wrapped with 'literalExample'
for every type that contains derivations. */
example =
if opt ? example
then (if hasDerivation
then (if isLiteralExample opt.example
then { example = detectDerivation decls opt.example; }
else throw "The attribute ${optionName}.example must be wrapped with 'literalExample' in ${concatStringsSep " and " decls}!")
else { example = detectDerivation decls opt.example; })
else {};
/* Enforce that the 'defaultText' attribute is defined for every option
that has a 'default' attribute that contains derivations. */
default =
if opt ? default
then (if hasDerivation
then (if isDefaultValue
then { default = opt.default; }
else (if opt ? defaultText
then { default = literalExample (detectDerivation decls opt.defaultText); }
else throw "The option ${optionName} requires a 'defaultText' attribute in ${concatStringsSep " and " decls}!"))
else (if opt ? defaultText
then (if maybeHiddenDerivation
then (if (let eval = builtins.tryEval (findDerivation opt.default); in eval.success && !eval.value)
then builtins.trace
"The attribute ${optionName}.defaultText might not be necessary in ${concatStringsSep " and " decls}!"
{ default = literalExample (detectDerivation decls opt.defaultText); }
else { default = literalExample (detectDerivation decls opt.defaultText); })
else builtins.trace
"The attribute ${optionName}.defaultText is not used and can be removed in ${concatStringsSep " and " decls}!"
{ default = detectDerivation decls opt.default; })
else { default = detectDerivation decls opt.default; }))
else {};
decls = filter (x: x != unknownModule) opt.declarations;
docOption = {
name = optionName;
description = opt.description or (throw "Option `${optionName}' has no description.");
declarations = decls;
internal = opt.internal or false;
visible = opt.visible or true;
readOnly = opt.readOnly or false;
} // example // default // subOptions // typeKeys;
typeKeys = if toDoc then { type = opt.type.name or null; } else { type = opt.type.typerep; keys = opt.loc; };
subOptions =
if toDoc
then {}
else let ss = opt.type.getSubOptions;
in if ss != {} then { suboptions = (extractOptionAttrSet false [] internalModuleConfig (ss internalModuleConfig)); } else {};
subOptionsDoc =
if toDoc
then let ss = opt.type.getSubOptionsPrefixed opt.loc;
in if ss != {} then extractOptionAttrSet true opt.loc internalModuleConfig (ss internalModuleConfig) else []
else [];
in
[ docOption ] ++ subOptionsDoc )
(filter (opt: (opt.visible or true) && !(opt.internal or false)) (collect isOption options));
/* This function recursively checks for derivations within an
an expression, and throws an error if a derivation or a
store path is found. The function is used to ensure that no
derivation leaks from the 'default' or 'example' attributes
of an option.
This makes the generation of `options.xml' much more efficient:
the XML representation of derivations is very large (on the
order of megabytes) and is not actually used by the manual
generator. */
detectDerivation = decl: x:
/* This function recursively removes all derivation attributes from
`x' except for the `name' attribute. This is to make the
generation of `options.xml' much more efficient: the XML
representation of derivations is very large (on the order of
megabytes) and is not actually used by the manual generator. */
scrubOptionValue = x:
if isDerivation x then
throw "Found unexpected derivation in '${x.name}' in '${concatStringsSep " and " decl}'!"
else if isString x && isStorePath x then
throw "Found unexpected store path in '${x.name}' in '${concatStringsSep " and " decl}'!"
else if isList x then map (detectDerivation decl) x
else if isAttrs x then mapAttrs (n: v: (detectDerivation decl) v) (removeAttrs x ["_args"])
{ type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
else if isList x then map scrubOptionValue x
else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
else x;
/* Same as detectDerivation, but returns a boolean instead of
throwing an exception. */
findDerivation = x:
if (isString x && isStorePath x) || isDerivation x then true
else if isList x then any findDerivation x
else if isAttrs x then any findDerivation (mapAttrsToList (_: v: v) (removeAttrs x ["_args"]))
else false;
/* For use in the example option attribute. It causes the given
text to be included verbatim in documentation. This is necessary
for example values that are not simple values, e.g.,
functions. */
# TODO: A more general name would probably be "literalNix".
literalExample = text: { _type = "literalExample"; inherit text; };
isLiteralExample = x: isAttrs x && hasAttr "_type" x && x._type == "literalExample";
/* Helper functions. */
showOption = concatStringsSep ".";

@ -1,8 +1,6 @@
# 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;
@ -23,8 +21,6 @@ rec {
mkOptionType =
{ # Human-readable representation of the type.
name
, # Parseable representation of the type.
typerep
, # Function applied to each definition that should return true if
# its type-correct, false otherwise.
check ? (x: true)
@ -35,59 +31,40 @@ rec {
# definition values and locations (e.g. [ { file = "/foo.nix";
# value = 1; } { file = "/bar.nix"; value = 2 } ]).
merge ? mergeDefaultOption
, # Return list of sub-options.
getSubOptions ? {}
, # Same as 'getSubOptions', but with extra information about the
# location of the option which is used to generate documentation.
getSubOptionsPrefixed ? null
, # 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
, # List of type representations (typerep) of all the elementary types
# that are nested within the type. For an elementary type the list is
# a singleton of the typerep of itself.
# NOTE: Must be specified for every container type!
nestedTypes ? null
, # List of all default values, and an empty list if no default value exists.
defaultValues ? []
}:
{ _type = "option-type";
inherit name typerep check merge getSubOptions getSubModules substSubModules defaultValues;
nestedTypes = if (isNull nestedTypes) then (singleton typerep) else nestedTypes;
getSubOptionsPrefixed = if (isNull getSubOptionsPrefixed) then (prefix: getSubOptions) else getSubOptionsPrefixed;
inherit name check merge getSubOptions getSubModules substSubModules;
};
types = rec {
#
# Elementary types
#
unspecified = mkOptionType {
name = "unspecified";
typerep = "(unspecified)";
};
bool = mkOptionType {
name = "boolean";
typerep = "(boolean)";
check = isBool;
merge = mergeEqualOption;
};
int = mkOptionType {
name = "integer";
typerep = "(integer)";
check = isInt;
merge = mergeOneOption;
};
str = mkOptionType {
name = "string";
typerep = "(string)";
check = isString;
merge = mergeOneOption;
};
@ -95,111 +72,73 @@ rec {
# Merge multiple definitions by concatenating them (with the given
# separator between the values).
separatedString = sep: mkOptionType {
name = "string" + (optionalString (sep != "") " separated by ${sep}");
typerep = "(separatedString(${escape ["(" ")"] sep}))";
name = "string";
check = isString;
merge = _module: loc: defs: concatStringsSep sep (getValues defs);
merge = loc: defs: concatStringsSep sep (getValues defs);
};
lines = separatedString "\n" // { typerep = "(lines)"; };
commas = separatedString "," // { typerep = "(commas)"; };
envVar = separatedString ":" // { typerep = "(envVar)"; };
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 "" // { typerep = "(string)"; };
string = separatedString "";
attrs = mkOptionType {
name = "attribute set";
typerep = "(attrs)";
check = isAttrs;
merge = _module: loc: foldl' (res: def: mergeAttrs res def.value) {};
merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
};
# derivation is a reserved keyword.
package = mkOptionType {
name = "package";
typerep = "(package)";
check = x: isDerivation x || isStorePath x;
merge = _module: loc: defs:
let res = mergeOneOption _module loc defs;
merge = loc: defs:
let res = mergeOneOption loc defs;
in if isDerivation res then res else toDerivation res;
};
# The correct type of packageSet would be:
# packageSet = attrsOf (either package packageSet)
# (Not sure if nix would allow to define a recursive type.)
# However, currently it is not possible to check that a packageSet actually
# contains packages (that is derivations). The check 'isDerivation' is too
# eager for the current implementation of the assertion mechanism and of the
# licenses control mechanism. That means it is not generally possible to go
# into the attribute set of packages to check that every attribute would
# evaluate to a derivation if the package would actually be evaluated. Maybe
# that restriction can be lifted in the future, but for now the content of
# the packageSet is not checked.
# TODO: The 'merge' function is copied from 'mergeDefaultOption' to keep
# backwards compatibility with the 'unspecified' type that was used for
# package sets previously. Maybe review if the merge function has to change.
packageSet = mkOptionType {
name = "derivation set";
typerep = "(packageSet)";
check = isAttrs;
merge = _module: loc: defs: foldl' mergeAttrs {} (map (x: x.value) defs);
};
path = mkOptionType {
name = "path";
typerep = "(path)";
# Hacky: there is no ‘isPath’ primop.
check = x: builtins.substring 0 1 (toString x) == "/";
merge = mergeOneOption;
};
#
# Container types
#
# 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";
typerep = "(listOf${elemType.typerep})";
check = isList;
merge = _module: loc: defs:
merge = loc: defs:
map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def: imap (m: def':
(mergeDefinitions _module
(mergeDefinitions
(loc ++ ["[definition ${toString n}-entry ${toString m}]"])
elemType
[{ inherit (def) file; value = def'; }]
).optionalValue
) def.value) defs)));
getSubOptions = elemType.getSubOptions;
getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed (prefix ++ ["*"]);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
getSubModules = elemType.getSubModules;
substSubModules = m: listOf (elemType.substSubModules m);
nestedTypes = elemType.nestedTypes;
defaultValues = [[]];
};
attrsOf = elemType: mkOptionType {
name = "attribute set of ${elemType.name}s";
typerep = "(attrsOf${elemType.typerep})";
check = isAttrs;
merge = _module: loc: defs:
merge = loc: defs:
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
(mergeDefinitions _module (loc ++ [name]) elemType defs).optionalValue
(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 = elemType.getSubOptions;
getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed (prefix ++ ["<name>"]);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: attrsOf (elemType.substSubModules m);
nestedTypes = elemType.nestedTypes;
defaultValues = [{}];
};
# List or attribute set of ...
@ -220,23 +159,18 @@ rec {
attrOnly = attrsOf elemType;
in mkOptionType {
name = "list or attribute set of ${elemType.name}s";
typerep = "(loaOf${elemType.typerep})";
check = x: isList x || isAttrs x;
merge = _module: loc: defs: attrOnly.merge _module loc (imap convertIfList defs);
getSubOptions = elemType.getSubOptions;
getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed (prefix ++ ["<name?>"]);
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: loaOf (elemType.substSubModules m);
nestedTypes = elemType.nestedTypes;
defaultValues = [{} []];
};
# List or element of ...
loeOf = elemType: mkOptionType {
name = "element or list of ${elemType.name}s";
typerep = "(loeOf${elemType.typerep})";
check = x: isList x || elemType.check x;
merge = _module: loc: defs:
merge = loc: defs:
let
defs' = filterOverrides defs;
res = (head defs').value;
@ -247,136 +181,81 @@ rec {
else if !isString res then
throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
else res;
nestedTypes = elemType.nestedTypes;
defaultValues = [[]] ++ elemType.defaultValues;
};
uniq = elemType: mkOptionType {
inherit (elemType) check;
name = "unique ${elemType.name}";
typerep = "(uniq${elemType.typerep})";
inherit (elemType) name check;
merge = mergeOneOption;
getSubOptions = elemType.getSubOptions;
getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed prefix;
getSubModules = elemType.getSubModules;
substSubModules = m: uniq (elemType.substSubModules m);
nestedTypes = elemType.nestedTypes;
defaultValues = elemType.defaultValues;
};
nullOr = elemType: mkOptionType {
name = "null or ${elemType.name}";
typerep = "(nullOr${elemType.typerep})";
check = x: x == null || elemType.check x;
merge = _module: loc: defs:
merge = loc: defs:
let nrNulls = count (def: def.value == null) 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 _module loc defs;
else elemType.merge loc defs;
getSubOptions = elemType.getSubOptions;
getSubOptionsPrefixed = prefix: elemType.getSubOptionsPrefixed prefix;
getSubModules = elemType.getSubModules;
substSubModules = m: nullOr (elemType.substSubModules m);
nestedTypes = elemType.nestedTypes;
defaultValues = [null] ++ elemType.defaultValues;
};
enum = values:
let
show = v:
if builtins.isString v then ''"${v}"''
else if builtins.isInt v then builtins.toString v
else ''<${builtins.typeOf v}>'';
in
mkOptionType {
name = "one of ${concatMapStringsSep ", " show values}";
typerep = "(enum${concatMapStrings (x: "(${escape ["(" ")"] (builtins.toString x)})") values})";
check = flip elem values;
merge = mergeOneOption;
nestedTypes = [];
};
either = t1: t2: mkOptionType {
name = "${t1.name} or ${t2.name}";
typerep = "(either${t1.typerep}${t2.typerep})";
check = x: t1.check x || t2.check x;
merge = mergeOneOption;
nestedTypes = t1.nestedTypes ++ t2.nestedTypes;
defaultValues = t1.defaultValues ++ t2.defaultValues;
};
#
# Complex types
#
submodule = opts:
let
opts' = toList opts;
inherit (import ./modules.nix) evalModules;
filterVisible = filter (opt: (if opt ? visible then opt.visible else true) && (if opt ? internal then !opt.internal else true));
in
mkOptionType rec {
name = "submodule";
typerep = "(submodule)";
check = x: isAttrs x || isFunction x;
merge = _module: loc: defs:
merge = loc: defs:
let
internalModule = [ { inherit _module; } { _module.args.name = lib.mkForce (last loc); } ];
coerce = def: if isFunction def then def else { config = def; };
modules = opts' ++ internalModule ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
in (evalModules {
inherit modules;
args.name = last loc;
prefix = loc;
}).config;
getSubOptions = getSubOptionsPrefixed [];
getSubOptionsPrefixed = prefix: _module:
let
getSubOptions = prefix: (evalModules
{ modules = opts'; inherit prefix;
# FIXME: hack to get shit to evaluate.
internalModule = [ { inherit _module; } { _module.args.name = lib.mkForce ""; } ];
in (evalModules {
modules = opts' ++ internalModule;
inherit prefix;
}).options;
args = { name = ""; }; }).options;
getSubModules = opts';
substSubModules = m: submodule m;
nestedTypes = concatMap (opt: opt.type.nestedTypes) (collect (lib.isType "option") (getSubOptions {}));
defaultValues = [{}];
};
enum = values:
let
show = v:
if builtins.isString v then ''"${v}"''
else if builtins.isInt v then builtins.toString v
else ''<${builtins.typeOf v}>'';
in
mkOptionType {
name = "one of ${concatMapStringsSep ", " show values}";
check = flip elem values;
merge = mergeOneOption;
};
#
# Legacy types
#
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";
typerep = "(optionSet)";
};
# Try to remove module options of that type wherever possible.
# A module option taking a function can not be introspected and documented properly.
functionTo = resultType:
mkOptionType {
name = "function to ${resultType.name}";
typerep = "(function${resultType.typerep})";
check = builtins.isFunction;
merge = mergeOneOption;
nestedTypes = resultType.nestedTypes;
defaultValues = map (x: {...}:x) resultType.nestedTypes; # INFO: It seems as nix can't compare functions, yet.
};
#
# misc
#
# Augment the given type with an additional type check function.
addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };

@ -34,8 +34,6 @@ in
system = eval.config.system.build.toplevel;
typechecker = eval.config.system.build.typechecker;
vm = vmConfig.system.build.vm;
vmWithBootLoader = vmWithBootLoaderConfig.system.build.vm;

@ -1,4 +1,4 @@
{ pkgs, options, internalModule, version, revision, extraSources ? [] }:
{ pkgs, options, version, revision, extraSources ? [] }:
with pkgs;
with pkgs.lib;
@ -6,10 +6,8 @@ with pkgs.lib;
let
# Remove invisible and internal options.
optionsList = filter (opt: opt.visible && !opt.internal) (optionAttrSetToDocList internalModule options);
optionsList = filter (opt: opt.visible && !opt.internal) (optionAttrSetToDocList options);
# INFO: Please add 'defaultText' or 'literalExample' to the option
# definition to avoid this substitution!
# Replace functions by the string <function>
substFunction = x:
if builtins.isAttrs x then mapAttrs (name: substFunction) x

@ -8,7 +8,7 @@
<para>An option declaration specifies the name, type and description
of a NixOS configuration option. It is illegal to define an option
that has not been declared in any module. A option declaration
that hasn’t been declared in any module. A option declaration
generally looks like this:
<programlisting>
@ -145,108 +145,6 @@ options = {
You can also create new types using the function
<varname>mkOptionType</varname>. See
<filename>lib/types.nix</filename> in Nixpkgs for details.
An option declaration must follow the following rules:
<itemizedlist mark='bullet'>
<listitem>
<para>A <varname>defaultText</varname> must be defined if and only if the type of the option
derives from <varname>package</varname>, <varname>packageSet</varname> or <varname>nixpkgsConfig
</varname>, and if and only if a <varname>default</varname> attribute is defined and if and only if
the value of the <varname>default</varname> attribute is not the default of the type of the
option declaration.
For example, a <varname>defaultText</varname> must be defined for
<programlisting>
type = types.listOf types.package;
default = [ pkgs.foo; ];
defaultText = "[ pkgs.foo; ]";
</programlisting>.
But no <varname>defaultText</varname> must be defined for
<programlisting>
type = types.listOf types.package;
default = [];
</programlisting>,
as <varname>[]</varname> is the default of <varname>types.listOf types.package</varname>.
</para>
</listitem>
<listitem>
<para>A <varname>defaultText</varname> can be defined if the type of the option derives from
<varname>path</varname> and if a <varname>default</varname> attribute is defined.</para>
</listitem>
<listitem>
<para>The value of the <varname>example</varname> attribute must be wrapped with <varname>
literalExample</varname> if the type of the option derives from <varname>package</varname>,
<varname>packageSet</varname> or <varname>nixpkgsConfig</varname>.</para>
</listitem>
<listitem>
<para>The value of <varname>defaultText</varname> and <varname>literalExample</varname> must
be a string which contains a valid nix expression. The nix expression has to evaluate in
<code>{pkgs}: <replaceable>value</replaceable></code>.
For example:
<itemizedlist>
<listitem>
<para>A <varname>defaultText</varname> could, e.g., be:
<programlisting>
type = types.package;
default = pkgs.foo;
defaultText = "pkgs.foo";
</programlisting>
But not <code>defaultText = "pkgs.foo;";</code>, as that
corresponds to <code>{pkgs}: pkgs.foo;</code>, which is an
invalid nix expression due to the ending with <varname>;</varname>.</para>
</listitem>
<listitem>
<para>A <varname>literalExample</varname> could be used as, e.g.:
<programlisting>
type = types.path;
example = literalExample "\"\${pkgs.bar}/bin/bar\"";
</programlisting>
But not <code>literalExample "\${pkgs.bar}/bin/bar";</code>, as that corresponds
to <code>{pkgs}: ${pkgs.bar}/bin/bar</code>, which is an invalid nix expression
as the <varname>path</varname> is not a <varname>string</varname> anymore.</para>
</listitem>
</itemizedlist></para>
</listitem>
<listitem>
<para>The <varname>type</varname> attribute must be defined for every option declaration.</para>
</listitem>
</itemizedlist>
NixOS ships the tool <varname>nixos-typecheck</varname> that can check an option declaration to:
<itemizedlist mark='bullet'>
<listitem>
<para>Enforce that an option declaration has a <varname>defaultText</varname> if and only if the
type of the option derives from <varname>package</varname>, <varname>packageSet</varname> or
<varname>nixpkgsConfig</varname> and if a <varname>default</varname> attribute is defined.</para>
</listitem>
<listitem>
<para>Enforce that the value of the <varname>example</varname> attribute is wrapped with <varname>
literalExample</varname> if the type of the option derives from <varname>package</varname>,
<varname>packageSet</varname> or <varname>nixpkgsConfig</varname>.</para>
</listitem>
<listitem>
<para>Warn if a <varname>defaultText</varname> is defined in an option declaration if the type of
the option does not derive from <varname>package</varname>, <varname>packageSet</varname> or
<varname>nixpkgsConfig</varname>.</para>
</listitem>
<listitem>
<para>Warn if no <varname>type</varname> is defined in an option declaration.</para>
</listitem>
</itemizedlist></para>
<filename>lib/types.nix</filename> in Nixpkgs for details.</para>
</section>

@ -20,13 +20,8 @@
, # !!! See comment about args in lib/modules.nix
specialArgs ? {}
, modules
, # Pass through a configuration of the internal modules declared
# in lib/modules.nix.
_module ? {}
, # !!! See comment about typeInference in lib/modules.nix
typeInference ? null
, # !!! See comment about check in lib/modules.nix
check ? null
check ? true
, prefix ? []
, lib ? import ../../lib
}:
@ -46,17 +41,13 @@ let
};
};
internalModule = { _module = (_module
// (if isNull check then {} else { inherit check; })
// (if isNull typeInference then {} else { inherit typeInference; })); };
in rec {
# Merge the option definitions in all modules, forming the full
# system configuration.
inherit (lib.evalModules {
inherit prefix;
modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ] ++ [ internalModule ];
inherit prefix check;
modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
args = extraArgs;
specialArgs = { modulesPath = ../modules; } // specialArgs;
}) config options;

@ -1,91 +0,0 @@
{ config, lib, pkgs, baseModules, ... }:
with pkgs;
with pkgs.lib;
let
optionsSpecs = inferenceMode:
let
versionModule =
{ system.nixosVersionSuffix = config.system.nixosVersionSuffix;
system.nixosRevision = config.system.nixosRevision;
nixpkgs.system = config.nixpkgs.system;
};
internalModule = { _module = config._module; } // (if isNull inferenceMode then {} else { _module.typeInference = mkForce inferenceMode; });
eval = evalModules {
modules = [ versionModule ] ++ baseModules ++ [ internalModule ];
args = (config._module.args) // { modules = [ ]; };
};
# Remove invisible and internal options.
optionsSpecs' = filter (opt: opt.visible && !opt.internal) (optionAttrSetToParseableSpecifications config._module eval.options);
# INFO: Please add 'defaultText' or 'literalExample' to the option
# definition to avoid this exception!
substFunction = key: decls: x:
if builtins.isAttrs x then mapAttrs (name: substFunction key decls) x
else if builtins.isList x then map (substFunction key decls) x
else if builtins.isFunction x then throw "Found an unexpected <function> in ${key} declared in ${concatStringsSep " and " decls}."
else x;
prefix = toString ../..;
stripPrefix = fn:
if substring 0 (stringLength prefix) fn == prefix then
substring (stringLength prefix + 1) 1000 fn
else
fn;
# Clean up declaration sites to not refer to the NixOS source tree.
cleanupOptions = x: flip map x (opt:
let substFunction' = y: substFunction opt.name opt.declarations y;
in opt
// { declarations = map (fn: stripPrefix fn) opt.declarations; }
// optionalAttrs (opt ? example) { example = substFunction' opt.example; }
// optionalAttrs (opt ? default) { default = substFunction' opt.default; }
// optionalAttrs (opt ? type) { type = substFunction' opt.type; });
in
cleanupOptions optionsSpecs';
in
{
system.build.typechecker = {
# The NixOS options as machine readable specifications in JSON format.
specifications = stdenv.mkDerivation {
name = "options-specs-json";
buildCommand = ''
# Export list of options in different format.
dst=$out/share/doc/nixos
mkdir -p $dst
cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON
(listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs null)))))
} $dst/options-specs.json
mkdir -p $out/nix-support
echo "file json $dst/options-specs.json" >> $out/nix-support/hydra-build-products
''; # */
meta.description = "List of NixOS options specifications in JSON format";
};
silent = listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs "silent"));
printAll = listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs "printAll"));
printUnspecified = listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) (optionsSpecs "printUnspecified"));
};
}

@ -64,7 +64,6 @@ in
consoleKeyMap = mkOption {
type = mkOptionType {
name = "string or path";
typerep = "(stringOrPath)";
check = t: (isString t || types.path.check t);
};

@ -6,7 +6,6 @@ let
sysctlOption = mkOptionType {
name = "sysctl option value";
typerep = "(sysctl)";
check = val:
let
checkType = x: isBool x || isString x || isInt x || isNull x;

@ -115,8 +115,8 @@ let
let name = head attrsNames; rest = tail attrsNames; in
if isOption result.options then
walkOptions rest {
options = result.options.type.getSubOptionsPrefix "";
opt = ''(\${result.opt}.type.getSubOptionsPrefix "")'';
options = result.options.type.getSubOptions "";
opt = ''(\${result.opt}.type.getSubOptions "")'';
cfg = ''\${result.cfg}."\${name}"'';
}
else

@ -1,52 +0,0 @@
#! /bin/sh
#! @shell@
if [ -x "@shell@" ]; then export SHELL="@shell@"; fi;
set -e
showSyntax() {
cat >&1 << EOF
nixos-typecheck
usage:
nixos-typecheck [action] [args]
where:
action = silent | printAll | printUnspecified | getSpecs
with default action: printUnspecified
args = any argument supported by nix-build
EOF
}
# Parse the command line.
extraArgs=()
action=printUnspecified
while [ "$#" -gt 0 ]; do
i="$1"; shift 1