lib/modules: Add class concept to check imports
This improves the error message when an incompatible module is imported.
This commit is contained in:
parent
3633bf98be
commit
b8ff2807a2
5 changed files with 69 additions and 4 deletions
|
@ -105,6 +105,10 @@ let
|
||||||
# when resolving module structure (like in imports). For everything else,
|
# when resolving module structure (like in imports). For everything else,
|
||||||
# there's _module.args. If specialArgs.modulesPath is defined it will be
|
# there's _module.args. If specialArgs.modulesPath is defined it will be
|
||||||
# used as the base path for disabledModules.
|
# used as the base path for disabledModules.
|
||||||
|
#
|
||||||
|
# `specialArgs.class`:
|
||||||
|
# A nominal type for modules. When set and non-null, this adds a check to
|
||||||
|
# make sure that only compatible modules are imported.
|
||||||
specialArgs ? {}
|
specialArgs ? {}
|
||||||
, # This would be remove in the future, Prefer _module.args option instead.
|
, # This would be remove in the future, Prefer _module.args option instead.
|
||||||
args ? {}
|
args ? {}
|
||||||
|
@ -256,6 +260,7 @@ let
|
||||||
|
|
||||||
merged =
|
merged =
|
||||||
let collected = collectModules
|
let collected = collectModules
|
||||||
|
(specialArgs.class or null)
|
||||||
(specialArgs.modulesPath or "")
|
(specialArgs.modulesPath or "")
|
||||||
(regularModules ++ [ internalModule ])
|
(regularModules ++ [ internalModule ])
|
||||||
({ inherit lib options config specialArgs; } // specialArgs);
|
({ inherit lib options config specialArgs; } // specialArgs);
|
||||||
|
@ -349,11 +354,11 @@ let
|
||||||
};
|
};
|
||||||
in result;
|
in result;
|
||||||
|
|
||||||
# collectModules :: (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
|
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
|
||||||
#
|
#
|
||||||
# Collects all modules recursively through `import` statements, filtering out
|
# Collects all modules recursively through `import` statements, filtering out
|
||||||
# all modules in disabledModules.
|
# all modules in disabledModules.
|
||||||
collectModules = let
|
collectModules = class: let
|
||||||
|
|
||||||
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
|
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
|
||||||
loadModule = args: fallbackFile: fallbackKey: m:
|
loadModule = args: fallbackFile: fallbackKey: m:
|
||||||
|
@ -364,6 +369,17 @@ let
|
||||||
throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
|
throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
|
||||||
else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
|
else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
|
||||||
|
|
||||||
|
checkModule =
|
||||||
|
if class != null
|
||||||
|
then
|
||||||
|
m:
|
||||||
|
if m.class != null -> m.class == class
|
||||||
|
then m
|
||||||
|
else
|
||||||
|
throw "The module ${m._file or m.key} was imported into ${class} instead of ${m.class}."
|
||||||
|
else
|
||||||
|
m: m;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Collects all modules recursively into the form
|
Collects all modules recursively into the form
|
||||||
|
|
||||||
|
@ -397,7 +413,7 @@ let
|
||||||
};
|
};
|
||||||
in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x:
|
in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x:
|
||||||
let
|
let
|
||||||
module = loadModule args parentFile "${parentKey}:anon-${toString n}" x;
|
module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
|
||||||
collectedImports = collectStructuredModules module._file module.key module.imports args;
|
collectedImports = collectStructuredModules module._file module.key module.imports args;
|
||||||
in {
|
in {
|
||||||
key = module.key;
|
key = module.key;
|
||||||
|
@ -461,7 +477,7 @@ let
|
||||||
else config;
|
else config;
|
||||||
in
|
in
|
||||||
if m ? config || m ? options then
|
if m ? config || m ? options then
|
||||||
let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in
|
let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType" "class"]; in
|
||||||
if badAttrs != {} then
|
if badAttrs != {} then
|
||||||
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
|
throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute."
|
||||||
else
|
else
|
||||||
|
@ -471,6 +487,7 @@ let
|
||||||
imports = m.imports or [];
|
imports = m.imports or [];
|
||||||
options = m.options or {};
|
options = m.options or {};
|
||||||
config = addFreeformType (addMeta (m.config or {}));
|
config = addFreeformType (addMeta (m.config or {}));
|
||||||
|
class = m.class or null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
# shorthand syntax
|
# shorthand syntax
|
||||||
|
@ -481,6 +498,7 @@ let
|
||||||
imports = m.require or [] ++ m.imports or [];
|
imports = m.require or [] ++ m.imports or [];
|
||||||
options = {};
|
options = {};
|
||||||
config = addFreeformType (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"]);
|
config = addFreeformType (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"]);
|
||||||
|
class = m.class or null;
|
||||||
};
|
};
|
||||||
|
|
||||||
applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
|
applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then
|
||||||
|
|
|
@ -360,6 +360,11 @@ checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survive
|
||||||
# because of an `extendModules` bug, issue 168767.
|
# because of an `extendModules` bug, issue 168767.
|
||||||
checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix
|
checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix
|
||||||
|
|
||||||
|
# Class checks
|
||||||
|
checkConfigOutput '^{ }$' config.ok.config ./class-check.nix
|
||||||
|
checkConfigError 'The module .*/module-class-is-darwin.nix was imported into nixos instead of darwin.' config.fail.config ./class-check.nix
|
||||||
|
checkConfigError 'The module foo.nix#darwinModules.default was imported into nixos instead of darwin.' config.fail-anon.config ./class-check.nix
|
||||||
|
|
||||||
# doRename works when `warnings` does not exist.
|
# doRename works when `warnings` does not exist.
|
||||||
checkConfigOutput '^1234$' config.c.d.e ./doRename-basic.nix
|
checkConfigOutput '^1234$' config.c.d.e ./doRename-basic.nix
|
||||||
# doRename adds a warning.
|
# doRename adds a warning.
|
||||||
|
|
34
lib/tests/modules/class-check.nix
Normal file
34
lib/tests/modules/class-check.nix
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{ lib, ... }: {
|
||||||
|
config = {
|
||||||
|
_module.freeformType = lib.types.anything;
|
||||||
|
ok =
|
||||||
|
lib.evalModules {
|
||||||
|
specialArgs.class = "nixos";
|
||||||
|
modules = [
|
||||||
|
./module-class-is-nixos.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
fail =
|
||||||
|
lib.evalModules {
|
||||||
|
specialArgs.class = "nixos";
|
||||||
|
modules = [
|
||||||
|
./module-class-is-nixos.nix
|
||||||
|
./module-class-is-darwin.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
fail-anon =
|
||||||
|
lib.evalModules {
|
||||||
|
specialArgs.class = "nixos";
|
||||||
|
modules = [
|
||||||
|
./module-class-is-nixos.nix
|
||||||
|
{ _file = "foo.nix#darwinModules.default";
|
||||||
|
class = "darwin";
|
||||||
|
imports = [];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
4
lib/tests/modules/module-class-is-darwin.nix
Normal file
4
lib/tests/modules/module-class-is-darwin.nix
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
class = "darwin";
|
||||||
|
config = {};
|
||||||
|
}
|
4
lib/tests/modules/module-class-is-nixos.nix
Normal file
4
lib/tests/modules/module-class-is-nixos.nix
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
class = "nixos";
|
||||||
|
config = {};
|
||||||
|
}
|
Loading…
Reference in a new issue