diff options
author | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2013-10-10 13:28:21 +0200 |
---|---|---|
committer | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2013-10-10 13:28:21 +0200 |
commit | 5fef92c4a0c91153e3edac3a61a232581765074a (patch) | |
tree | 291d684d0ef71e200e6d8ab5c33fc1aca467cbb3 /lib/modules.nix | |
parent | 2a537fb369d1479748fe233261eaadfa5c2fa930 (diff) |
Move pkgs/lib/ to lib/
Diffstat (limited to 'lib/modules.nix')
-rw-r--r-- | lib/modules.nix | 380 |
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; + +} |