Commit b8ff2807 authored by Robert Hensing's avatar Robert Hensing
Browse files

lib/modules: Add class concept to check imports

This improves the error message when an incompatible module is
imported.
parent 3633bf98
Loading
Loading
Loading
Loading
+22 −4
Original line number Diff line number Diff line
@@ -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
+5 −0
Original line number Diff line number Diff line
@@ -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.
+34 −0
Original line number Diff line number Diff line
{ 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 −0
Original line number Diff line number Diff line
{
  class = "darwin";
  config = {};
}
+4 −0
Original line number Diff line number Diff line
{
  class = "nixos";
  config = {};
}