From b8ff2807a29861236a7ac3ed01c4565ba725e1b1 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 24 Oct 2022 14:30:19 +0200 Subject: [PATCH] lib/modules: Add class concept to check imports This improves the error message when an incompatible module is imported. --- lib/modules.nix | 26 ++++++++++++--- lib/tests/modules.sh | 5 +++ lib/tests/modules/class-check.nix | 34 ++++++++++++++++++++ lib/tests/modules/module-class-is-darwin.nix | 4 +++ lib/tests/modules/module-class-is-nixos.nix | 4 +++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 lib/tests/modules/class-check.nix create mode 100644 lib/tests/modules/module-class-is-darwin.nix create mode 100644 lib/tests/modules/module-class-is-nixos.nix diff --git a/lib/modules.nix b/lib/modules.nix index 0a842aa0063e..1e8f085e6f4f 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -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 diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 2af58ff5db9f..073dc6054860 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -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. diff --git a/lib/tests/modules/class-check.nix b/lib/tests/modules/class-check.nix new file mode 100644 index 000000000000..6e02f8c30920 --- /dev/null +++ b/lib/tests/modules/class-check.nix @@ -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 = []; + } + ]; + }; + + }; +} diff --git a/lib/tests/modules/module-class-is-darwin.nix b/lib/tests/modules/module-class-is-darwin.nix new file mode 100644 index 000000000000..d8b60203f707 --- /dev/null +++ b/lib/tests/modules/module-class-is-darwin.nix @@ -0,0 +1,4 @@ +{ + class = "darwin"; + config = {}; +} diff --git a/lib/tests/modules/module-class-is-nixos.nix b/lib/tests/modules/module-class-is-nixos.nix new file mode 100644 index 000000000000..04b6e860e890 --- /dev/null +++ b/lib/tests/modules/module-class-is-nixos.nix @@ -0,0 +1,4 @@ +{ + class = "nixos"; + config = {}; +}