summaryrefslogtreecommitdiffstats
path: root/lib/modules.nix
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:21 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:21 +0200
commit5fef92c4a0c91153e3edac3a61a232581765074a (patch)
tree291d684d0ef71e200e6d8ab5c33fc1aca467cbb3 /lib/modules.nix
parent2a537fb369d1479748fe233261eaadfa5c2fa930 (diff)
Move pkgs/lib/ to lib/
Diffstat (limited to 'lib/modules.nix')
-rw-r--r--lib/modules.nix380
1 files changed, 380 insertions, 0 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
new file mode 100644
index 000000000000..acd10e7bf576
--- /dev/null
+++ b/lib/modules.nix
@@ -0,0 +1,380 @@
+# NixOS module handling.
+
+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 {
+
+ # Unfortunately this can also be a string.
+ isPath = x: !(
+ builtins.isFunction x
+ || builtins.isAttrs x
+ || 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
+ delayedModule = delayProperties m;
+
+ getImports =
+ toList (rmProperties (delayedModule.require or []));
+ getImportedPaths = filter isPath getImports;
+ getImportedSets = filter (x: !isPath x) getImports;
+
+ getConfig =
+ removeAttrs delayedModule ["require" "key" "imports"];
+
+ in
+ if isModule m then
+ { key = "<unknown location>"; } // m
+ else
+ { 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 [];
+
+ newModuleName = origin: index:
+ "${origin.key}:<import-${toString index}>";
+
+ topLevel = {
+ key = "<top-level>";
+ };
+
+ in
+ (lazyGenericClosure {
+ startSet = imap (moduleImport topLevel) initModules;
+ operator = m: imap (moduleImport m) (getImports m);
+ });
+
+
+ moduleApply = funs: module:
+ lib.mapAttrs (name: value:
+ if builtins.hasAttr name funs then
+ let fun = lib.getAttr name funs; in
+ fun value
+ else
+ value
+ ) module;
+
+
+ # Handle mkMerge function left behind after a delay property.
+ moduleFlattenMerge = module:
+ if module ? config &&
+ isProperty module.config &&
+ isMerge module.config.property
+ then
+ (map (cfg: { key = module.key; config = cfg; }) module.config.content)
+ ++ [ (module // { config = {}; }) ]
+ else
+ [ module ];
+
+
+ # Handle mkMerge attributes which are left behind by previous delay
+ # properties and convert them into a list of modules. Delay properties
+ # 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 modules_ = modules; in
+ let
+ addName = name:
+ if path == "" then name else path + "." + name;
+
+ modules = concatLists (map delayModule modules_);
+
+ modulesOf = name: filterModules name modules;
+ declarationsOf = name: filter (m: m ? options) (modulesOf name);
+ definitionsOf = name: filter (m: m ? config ) (modulesOf name);
+
+ recurseInto = name:
+ moduleMerge (addName name) (modulesOf name);
+
+ recurseForOption = name: modules: args:
+ moduleMerge name (
+ moduleClosure modules args
+ );
+
+ errorSource = modules:
+ "The error may come from the following files:\n" + (
+ lib.concatStringsSep "\n" (
+ map (m:
+ if m ? key then toString m.key else "<unknown location>"
+ ) modules
+ )
+ );
+
+ 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 = {}; };
+
+ in if modules == [] then endRecursion else
+ getResults (fix (crossResults: moduleZip {
+ options = lib.zipWithNames allNames (name: values: rec {
+ config = lib.getAttr name crossResults.config;
+
+ declarations = declarationsOf name;
+ declarationSources =
+ map (m: {
+ source = m.key;
+ }) declarations;
+
+ hasOptions = values != [];
+ isOption = any lib.isOption values;
+
+ decls = # add location to sub-module options.
+ map (m:
+ mapSubOptions
+ (unifyOptionModule {inherit (m) key;} name)
+ m.options
+ ) declarations;
+
+ decl =
+ lib.addErrorContext "${eol
+ }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
+ throw "${eol
+ }Unexpected type where option declarations are expected.${eol
+ }${errorSource declarations}${eol
+ }";
+
+ });
+
+ config = lib.zipWithNames allNames (name: values_: rec {
+ option = lib.getAttr name crossResults.options;
+
+ definitions = definitionsOf name;
+ definitionSources =
+ map (m: {
+ source = m.key;
+ value = m.config;
+ }) definitions;
+
+ values = values_ ++
+ optionals (option.isOption && option.decl ? extraConfigs)
+ option.decl.extraConfigs;
+
+ defs = evalDefinitions option.decl values;
+
+ isNotDefined = defs == [];
+
+ value =
+ 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
+ eol = "\n";
+ addName = name:
+ if path == "" then name else path + "." + name;
+ in
+ if lib.isOption options then
+ if options ? options then
+ options.type.fold
+ (cfg: res: res && checkModule (options.type.docPath path) cfg._args)
+ true config
+ else
+ true
+ else if isAttrs options && lib.attrNames m.options != [] then
+ all (name:
+ lib.addErrorContext "${eol
+ }while checking the attribute `${addName name}':${eol
+ }" (checkModule (addName name) (selectModule name m))
+ ) (lib.attrNames m.config)
+ else
+ builtins.trace "try to evaluate config ${lib.showVal config}."
+ false;
+
+}