Merge pull request #76857 from Infinisil/recursive-disableModules

Apply `disabledModules` recursively

Changed files
+120 -31
lib
nixos
doc
manual
development
modules
+2 -2
lib/default.nix
···
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
···
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'
+73 -25
lib/modules.nix
···
};
};
-
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
···
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);
+
+
/*
+
Collects all modules recursively into the form
-
/* 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;
-
};
+
{
+
disabled = [ <list of disabled modules> ];
+
# All modules of the main module list
+
modules = [
+
{
+
key = <key1>;
+
module = <module for key1>;
+
# All modules imported by the module for key1
+
modules = [
+
{
+
key = <key1-1>;
+
module = <module for key1-1>;
+
# 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. */
+6
lib/tests/modules.sh
···
# 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 <<EOF
====== module tests ======
$pass Pass
+5
lib/tests/modules/disable-recursive/bar.nix
···
+
{
+
imports = [
+
../declare-enable.nix
+
];
+
}
+7
lib/tests/modules/disable-recursive/disable-bar.nix
···
+
{
+
+
disabledModules = [
+
./bar.nix
+
];
+
+
}
+7
lib/tests/modules/disable-recursive/disable-foo.nix
···
+
{
+
+
disabledModules = [
+
./foo.nix
+
];
+
+
}
+5
lib/tests/modules/disable-recursive/foo.nix
···
+
{
+
imports = [
+
../declare-enable.nix
+
];
+
}
+8
lib/tests/modules/disable-recursive/main.nix
···
+
{
+
imports = [
+
./foo.nix
+
./bar.nix
+
];
+
+
enable = true;
+
}
+2 -2
nixos/doc/manual/development/replace-modules.xml
···
<title>Replace Modules</title>
<para>
-
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.
</para>
+5 -2
nixos/modules/misc/documentation.nix
···
-
{ config, lib, pkgs, baseModules, extraModules, modules, ... }:
+
{ config, lib, pkgs, baseModules, extraModules, modules, modulesPath, ... }:
with lib;
···
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: