lib/modules: Add class concept to check imports

This improves the error message when an incompatible module is
imported.
This commit is contained in:
Robert Hensing 2022-10-24 14:30:19 +02:00
parent 3633bf98be
commit b8ff2807a2
5 changed files with 69 additions and 4 deletions

View file

@ -105,6 +105,10 @@ let
# when resolving module structure (like in imports). For everything else,
# there's _module.args. If specialArgs.modulesPath is defined it will be
# 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 ? {}
, # This would be remove in the future, Prefer _module.args option instead.
args ? {}
@ -256,6 +260,7 @@ let
merged =
let collected = collectModules
(specialArgs.class or null)
(specialArgs.modulesPath or "")
(regularModules ++ [ internalModule ])
({ inherit lib options config specialArgs; } // specialArgs);
@ -349,11 +354,11 @@ let
};
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
# all modules in disabledModules.
collectModules = let
collectModules = class: let
# Like unifyModuleSyntax, but also imports paths and calls functions if necessary
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}"
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
@ -397,7 +413,7 @@ let
};
in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x:
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;
in {
key = module.key;
@ -461,7 +477,7 @@ let
else config;
in
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
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
@ -471,6 +487,7 @@ let
imports = m.imports or [];
options = m.options or {};
config = addFreeformType (addMeta (m.config or {}));
class = m.class or null;
}
else
# shorthand syntax
@ -481,6 +498,7 @@ let
imports = m.require or [] ++ m.imports or [];
options = {};
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

View file

@ -360,6 +360,11 @@ checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survive
# because of an `extendModules` bug, issue 168767.
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.
checkConfigOutput '^1234$' config.c.d.e ./doRename-basic.nix
# doRename adds a warning.

View 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 = [];
}
];
};
};
}

View file

@ -0,0 +1,4 @@
{
class = "darwin";
config = {};
}

View file

@ -0,0 +1,4 @@
{
class = "nixos";
config = {};
}