diff --git a/lib/default.nix b/lib/default.nix index 7ea118d51..857a8d14b 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -43,6 +43,8 @@ lib.fix (clanLib: { inventory = clanLib.callLib ./inventory { }; modules = clanLib.callLib ./inventory/frontmatter { }; test = clanLib.callLib ./test { }; + # Custom types + types = clanLib.callLib ./types { }; # Plain imports. introspection = import ./introspection { inherit lib; }; diff --git a/lib/flake-module.nix b/lib/flake-module.nix index bb4e91c99..abf55216a 100644 --- a/lib/flake-module.nix +++ b/lib/flake-module.nix @@ -11,6 +11,7 @@ rec { ./introspection/flake-module.nix ./inventory/flake-module.nix ./jsonschema/flake-module.nix + ./types/flake-module.nix ]; flake.clanLib = import ./default.nix { inherit lib inputs self; diff --git a/lib/types/default.nix b/lib/types/default.nix new file mode 100644 index 000000000..868a83541 --- /dev/null +++ b/lib/types/default.nix @@ -0,0 +1,28 @@ +{ lib, ... }: +{ + uniqueDeferredSerializableModule = lib.fix ( + self: + # Essentially the "raw" type, but with a custom name and check + lib.mkOptionType { + name = "deferredModule"; + description = "deferred module that has custom check and merge behavior"; + descriptionClass = "noun"; + # Unfortunately, tryEval doesn't catch JSON errors + check = value: lib.seq (builtins.toJSON value) true; + merge = lib.options.mergeUniqueOption { + message = "------"; + merge = loc: defs: { + imports = map ( + def: lib.setDefaultModuleLocation "${def.file}, via option ${lib.showOption loc}" def.value + ) defs; + }; + }; + functor = { + inherit (self) name; + type = self; + # Non mergable type + binOp = _a: _b: null; + }; + } + ); +} diff --git a/lib/types/flake-module.nix b/lib/types/flake-module.nix new file mode 100644 index 000000000..2b75953c5 --- /dev/null +++ b/lib/types/flake-module.nix @@ -0,0 +1,25 @@ +{ self, inputs, ... }: +{ + perSystem = + { ... }: + let + # Module that contains the tests + # This module adds: + # - legacyPackages..eval-tests-hello-world + # - checks..eval-tests-hello-world + test-types-module = ( + self.clanLib.test.flakeModules.makeEvalChecks { + module = throw ""; + inherit self inputs; + testName = "types"; + tests = ./tests.nix; + # Optional arguments passed to the test + testArgs = { }; + } + ); + in + { + imports = [ test-types-module ]; + legacyPackages.xxx = { }; + }; +} diff --git a/lib/types/tests.nix b/lib/types/tests.nix new file mode 100644 index 000000000..268292356 --- /dev/null +++ b/lib/types/tests.nix @@ -0,0 +1,41 @@ +{ lib, clanLib, ... }: +let + evalSettingsModule = + m: + lib.evalModules { + modules = [ + { + options.foo = lib.mkOption { + type = clanLib.types.uniqueDeferredSerializableModule; + }; + } + m + ]; + }; +in +{ + test_1 = + let + eval = evalSettingsModule { + foo = { }; + }; + in + { + inherit eval; + expr = eval.config.foo; + expected = { + # Foo has imports + # This can only ever be one module due to the type of foo + imports = [ + { + # This is the result of 'setDefaultModuleLocation' + # Which also returns exactly one module + _file = ", via option foo"; + imports = [ + { } + ]; + } + ]; + }; + }; +}