diff --git a/lib/default.nix b/lib/default.nix index e31edeaaf9e..9f7a088d792 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 aebc9874aa4..38d6ac8cd91 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. */ diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index f69befd15c6..2997fb1ada1 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -177,6 +177,12 @@ checkConfigOutput "true" config.submodule.outer ./declare-submoduleWith-modules. # Temporarily disabled until https://github.com/NixOS/nixpkgs/pull/76861 #checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix +# Check that disabledModules works recursively and correctly +checkConfigOutput "true" config.enable ./disable-recursive/main.nix +checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo.nix} +checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix} +checkConfigError 'The option .* defined in .* does not exist' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix} + cat <Replace Modules - Modules that are imported can also be disabled. The option declarations and - config implementation of a disabled module will be ignored, allowing another + Modules that are imported can also be disabled. The option declarations, + config implementation and the imports of a disabled module will be ignored, allowing another to take it's place. This can be used to import a set of modules from another channel while keeping the rest of the system on a stable release. diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix index 820553270e3..d09afadd609 100644 --- a/nixos/modules/misc/documentation.nix +++ b/nixos/modules/misc/documentation.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, baseModules, extraModules, modules, ... }: +{ config, lib, pkgs, baseModules, extraModules, modules, modulesPath, ... }: with lib; @@ -22,7 +22,10 @@ let scrubbedEval = evalModules { modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ manualModules; args = (config._module.args) // { modules = [ ]; }; - specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; }; + specialArgs = { + pkgs = scrubDerivations "pkgs" pkgs; + inherit modulesPath; + }; }; scrubDerivations = namePrefix: pkgSet: mapAttrs (name: value: