diff --git a/lib/default.nix b/lib/default.nix index e1ab88473..b48b4582e 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -15,6 +15,7 @@ in buildClan = import ./build-clan { inherit lib nixpkgs clan-core; }; facts = import ./facts.nix { inherit lib; }; inventory = import ./inventory { inherit lib clan-core; }; + values = import ./values { inherit lib; }; jsonschema = import ./jsonschema { inherit lib; }; modules = import ./frontmatter { inherit lib; diff --git a/lib/flake-module.nix b/lib/flake-module.nix index e9ab69a54..ccfdf3151 100644 --- a/lib/flake-module.nix +++ b/lib/flake-module.nix @@ -9,6 +9,7 @@ ./jsonschema/flake-module.nix ./inventory/flake-module.nix ./build-clan/flake-module.nix + ./values/flake-module.nix ]; flake.lib = import ./default.nix { inherit lib inputs; diff --git a/lib/values/default.nix b/lib/values/default.nix new file mode 100644 index 000000000..05dcdebe1 --- /dev/null +++ b/lib/values/default.nix @@ -0,0 +1,90 @@ +{ + lib ? import , +}: +let + filterOptions = lib.filterAttrs ( + name: _: + !builtins.elem name [ + "_module" + "_freeformOptions" + ] + ); + + getPrios = + { + options, + }: + let + filteredOptions = filterOptions options; + in + lib.mapAttrs ( + _: opt: + let + prio = { + __prio = opt.highestPrio; + }; + subOptions = opt.type.getSubOptions opt.loc; + + attrDefinitions = (lib.modules.mergeAttrDefinitionsWithPrio opt); + zipDefs = builtins.zipAttrsWith (_ns: vs: vs); + defs = zipDefs opt.definitions; + + prioPerValue = + { type, defs }: + lib.mapAttrs ( + attrName: prioSet: + let + # Evaluate the submodule + options = filterOptions subOptions; + modules = ( + [ + { inherit options; } + ] + ++ map (config: { inherit config; }) defs.${attrName} + ); + submoduleEval = lib.evalModules { + inherit modules; + }; + in + (lib.optionalAttrs (prioSet ? highestPrio) { + __prio = prioSet.highestPrio; + # inherit defs options; + }) + // ( + if type.nestedTypes.elemType.name == "submodule" then + getPrios { options = submoduleEval.options; } + else + # Nested attrsOf + (lib.optionalAttrs (type.nestedTypes.elemType.name == "attrsOf") ( + prioPerValue { + type = type.nestedTypes.elemType; + defs = zipDefs defs.${attrName}; + } prioSet.value + )) + ) + ); + + attributePrios = prioPerValue { + type = opt.type; + inherit defs; + } attrDefinitions; + in + if opt ? type && opt.type.name == "submodule" then + prio // (getPrios { options = subOptions; }) + else if opt ? type && opt.type.name == "attrsOf" then + # prio // attributePrios + # else if + # opt ? type && opt.type.name == "attrsOf" && opt.type.nestedTypes.elemType.name == "attrsOf" + # then + # prio // attributePrios + # else if opt ? type && opt.type.name == "attrsOf" then + prio // attributePrios + else if opt ? type && opt._type == "option" then + prio + else + getPrios { options = opt; } + ) filteredOptions; +in +{ + inherit getPrios; +} diff --git a/lib/values/flake-module.nix b/lib/values/flake-module.nix new file mode 100644 index 000000000..db60f92c5 --- /dev/null +++ b/lib/values/flake-module.nix @@ -0,0 +1,24 @@ +{ self, inputs, ... }: +let + inputOverrides = builtins.concatStringsSep " " ( + builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs) + ); +in +{ + perSystem = + { pkgs, system, ... }: + { + checks = { + lib-values-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } '' + export HOME="$(realpath .)" + + nix-unit --eval-store "$HOME" \ + --extra-experimental-features flakes \ + ${inputOverrides} \ + --flake ${self}#legacyPackages.${system}.evalTests-inventory + + touch $out + ''; + }; + }; +} diff --git a/lib/values/test.nix b/lib/values/test.nix new file mode 100644 index 000000000..b18c85179 --- /dev/null +++ b/lib/values/test.nix @@ -0,0 +1,211 @@ +# tests for the nixos options to jsonschema converter +# run these tests via `nix-unit ./test.nix` +{ + lib ? (import { }).lib, + slib ? (import ./. { inherit lib; }), +}: +let + eval = + modules: + let + evaledConfig = lib.evalModules { + inherit modules; + }; + in + evaledConfig; +in +{ + test_default = { + expr = slib.getPrios { + options = + (eval [ + { + options.foo.bar = lib.mkOption { + type = lib.types.bool; + description = "Test Description"; + default = true; + }; + } + ]).options; + }; + expected = { + foo.bar = { + __prio = 1500; + }; + }; + }; + test_no_default = { + expr = slib.getPrios { + options = + (eval [ + { + options.foo.bar = lib.mkOption { + type = lib.types.bool; + }; + } + ]).options; + }; + expected = { + foo.bar = { + __prio = 9999; + }; + }; + }; + + test_submodule = { + expr = slib.getPrios { + options = + (eval [ + { + options.foo = lib.mkOption { + type = lib.types.submodule { + options = { + bar = lib.mkOption { + type = lib.types.bool; + }; + }; + }; + }; + } + ]).options; + }; + expected = { + foo = { + # Prio of the submodule itself + __prio = 9999; + + # Prio of the bar option within the submodule + bar.__prio = 9999; + }; + }; + }; + + # TODO(@hsjobeki): Cover this edge case + # test_freeform = + # let + # evaluated = ( + # eval [ + # { + # freeformType = with lib.types; attrsOf (int); + # options = { + # foo = lib.mkOption { + # type = lib.types.int; + # default = 0; + # }; + # }; + # } + # { + # bar = lib.mkForce 123; + # baz = 1; + # } + # { + # bar = 10; + # } + # ] + # ); + # in + # { + # inherit evaluated; + # expr = slib.getPrios { + # options = evaluated.options; + # }; + # expected = { + # }; + # }; + + test_attrsOf_submodule = + let + evaluated = eval [ + { + options.foo = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + bar = lib.mkOption { + type = lib.types.int; + default = 0; + }; + }; + } + ); + }; + config.foo = { + "nested" = { + "bar" = 2; # <- 100 prio ? + }; + "other" = { + "bar" = lib.mkForce 2; # <- 50 prio ? + }; + }; + } + ]; + in + { + expr = slib.getPrios { options = evaluated.options; }; + expected = { + foo.__prio = 100; + + foo.nested.__prio = 100; + foo.other.__prio = 100; + + foo.nested.bar.__prio = 100; + foo.other.bar.__prio = 50; + }; + }; + test_attrsOf_attrsOf_submodule = + let + evaluated = eval [ + { + options.foo = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.attrsOf ( + lib.types.submodule { + options = { + bar = lib.mkOption { + type = lib.types.int; + default = 0; + }; + }; + } + ) + ); + }; + config.foo = { + a.b = { + bar = 1; + }; + a.c = { + bar = 1; + }; + x.y = { + bar = 1; + }; + x.z = { + bar = 1; + }; + }; + } + ]; + in + { + inherit evaluated; + expr = slib.getPrios { options = evaluated.options; }; + expected = { + foo.__prio = 100; + + # Sub A + foo.a.__prio = 100; + # a.b doesnt have a prio + # a.c doesnt have a prio + foo.a.b.bar.__prio = 100; + foo.a.c.bar.__prio = 100; + + # Sub X + foo.x.__prio = 100; + # x.y doesnt have a prio + # x.z doesnt have a prio + foo.x.y.bar.__prio = 100; + foo.x.z.bar.__prio = 100; + }; + }; +}