diff --git a/lib/default.nix b/lib/default.nix index e31edeaaf9ef..9f7a088d792d 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -101,7 +101,7 @@ let cleanSource sourceByRegex sourceFilesBySuffices commitIdFromGitRepo cleanSourceWith pathHasContext canCleanSource; - inherit (modules) evalModules closeModules unifyModuleSyntax + inherit (modules) evalModules unifyModuleSyntax applyIfFunction mergeModules mergeModules' mergeOptionDecls evalOptionValue mergeDefinitions pushDownProperties dischargeProperties filterOverrides @@ -110,7 +110,7 @@ let mkFixStrictness mkOrder mkBefore mkAfter mkAliasDefinitions mkAliasAndWrapDefinitions fixMergeModules mkRemovedOptionModule mkRenamedOptionModule mkMergedOptionModule mkChangedOptionModule - mkAliasOptionModule doRename filterModules; + mkAliasOptionModule doRename; inherit (options) isOption mkEnableOption mkSinkUndeclaredOptions mergeDefaultOption mergeOneOption mergeEqualOption getValues getFiles optionAttrSetToDocList optionAttrSetToDocList' diff --git a/lib/modules.nix b/lib/modules.nix index 48788ae933dd..9e4b20479019 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -59,9 +59,12 @@ rec { }; }; - closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options lib; } // specialArgs); + collected = collectModules + (specialArgs.modulesPath or "") + (modules ++ [ internalModule ]) + ({ inherit config options lib; } // specialArgs); - options = mergeModules prefix (reverseList (filterModules (specialArgs.modulesPath or "") closed)); + options = mergeModules prefix (reverseList collected); # Traverse options and extract the option values into the final # config set. At the same time, check whether all option @@ -87,31 +90,76 @@ rec { result = { inherit options config; }; in result; + # collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ] + # + # Collects all modules recursively through `import` statements, filtering out + # all modules in disabledModules. + collectModules = let - # Filter disabled modules. Modules can be disabled allowing - # their implementation to be replaced. - filterModules = modulesPath: modules: - let - moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m; - disabledKeys = map moduleKey (concatMap (m: m.disabledModules) modules); - in - filter (m: !(elem m.key disabledKeys)) modules; + # Like unifyModuleSyntax, but also imports paths and calls functions if necessary + loadModule = args: fallbackFile: fallbackKey: m: + if isFunction m || isAttrs m then + unifyModuleSyntax fallbackFile fallbackKey (applyIfFunction fallbackKey m args) + else unifyModuleSyntax (toString m) (toString m) (applyIfFunction (toString m) (import m) args); - /* Close a set of modules under the ‘imports’ relation. */ - closeModules = modules: args: - let - toClosureList = file: parentKey: imap1 (n: x: - if isAttrs x || isFunction x then - let key = "${parentKey}:anon-${toString n}"; in - unifyModuleSyntax file key (applyIfFunction key x args) - else - let file = toString x; key = toString x; in - unifyModuleSyntax file key (applyIfFunction key (import x) args)); - in - builtins.genericClosure { - startSet = toClosureList unknownModule "" modules; - operator = m: toClosureList m._file m.key m.imports; - }; + /* + Collects all modules recursively into the form + + { + disabled = [ ]; + # All modules of the main module list + modules = [ + { + key = ; + module = ; + # All modules imported by the module for key1 + modules = [ + { + key = ; + module = ; + # All modules imported by the module for key1-1 + modules = [ ... ]; + } + ... + ]; + } + ... + ]; + } + */ + collectStructuredModules = + let + collectResults = modules: { + disabled = concatLists (catAttrs "disabled" modules); + inherit modules; + }; + in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x: + let + module = loadModule args parentFile "${parentKey}:anon-${toString n}" x; + collectedImports = collectStructuredModules module._file module.key module.imports args; + in { + key = module.key; + module = module; + modules = collectedImports.modules; + disabled = module.disabledModules ++ collectedImports.disabled; + }) initialModules); + + # filterModules :: String -> { disabled, modules } -> [ Module ] + # + # Filters a structure as emitted by collectStructuredModules by removing all disabled + # modules recursively. It returns the final list of unique-by-key modules + filterModules = modulesPath: { disabled, modules }: + let + moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m; + disabledKeys = listToAttrs (map (k: nameValuePair (moduleKey k) null) disabled); + keyFilter = filter (attrs: ! disabledKeys ? ${attrs.key}); + in map (attrs: attrs.module) (builtins.genericClosure { + startSet = keyFilter modules; + operator = attrs: keyFilter attrs.modules; + }); + + in modulesPath: initialModules: args: + filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args); /* Massage a module into canonical form, that is, a set consisting of ‘options’, ‘config’ and ‘imports’ attributes. */