about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRobert Hensing <robert@roberthensing.nl>2022-10-24 14:30:19 +0200
committerRobert Hensing <robert@roberthensing.nl>2023-05-06 18:29:03 +0200
commitb8ff2807a29861236a7ac3ed01c4565ba725e1b1 (patch)
tree6ec2d3559dc0e6c73e9a5fe725ce35614dd30ec5
parent3633bf98be70af326056ebc87a9adedd0e8a24c7 (diff)
lib/modules: Add class concept to check imports
This improves the error message when an incompatible module is
imported.
-rw-r--r--lib/modules.nix26
-rwxr-xr-xlib/tests/modules.sh5
-rw-r--r--lib/tests/modules/class-check.nix34
-rw-r--r--lib/tests/modules/module-class-is-darwin.nix4
-rw-r--r--lib/tests/modules/module-class-is-nixos.nix4
5 files changed, 69 insertions, 4 deletions
diff --git a/lib/modules.nix b/lib/modules.nix
index 0a842aa0063e1..1e8f085e6f4f8 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 2af58ff5db9f0..073dc60548603 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 0000000000000..6e02f8c309208
--- /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 0000000000000..d8b60203f707e
--- /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 0000000000000..04b6e860e8900
--- /dev/null
+++ b/lib/tests/modules/module-class-is-nixos.nix
@@ -0,0 +1,4 @@
+{
+  class = "nixos";
+  config = {};
+}