diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 75b316c972120..03f13f3073d30 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -94,6 +94,23 @@ checkConfigOutput '^42$' config.value ./declare-oneOf.nix ./define-value-int-pos checkConfigOutput '^\[ \]$' config.value ./declare-oneOf.nix ./define-value-list.nix checkConfigOutput '^"24"$' config.value ./declare-oneOf.nix ./define-value-string.nix +test_flake_type() { + # A flake must be returned without any pre or post-processing. + checkConfigOutput "^true$" config.result ./declare-flake.nix ./define-flake.nix + + # Even an identical flake can not be merged. In the "best" case it's the same flake, + # but we'd have to check that is. Perhaps some attributes could be merged, but + # many can't, so this would effectively create two _usage_ types: + # - full access, allowing access to `sourceInfo` and `packages.default` + # - only access to attributes that can be expected to be merged, such as + # `packages.foo` + # However, the latter would still break the rule that an addition somewhere "unrelated" + # doesn't break anything. So merging would be a bad idea. + checkConfigError "The option .flake. is defined multiple times" config.result ./declare-flake.nix ./define-flake.nix ./define-flake-again.nix + checkConfigError "However, flakes can not be merged" config.result ./declare-flake.nix ./define-flake.nix ./define-flake-again.nix +} +test_flake_type + # Check mkForce without submodules. set -- config.enable ./declare-enable.nix ./define-enable.nix checkConfigOutput '^true$' "$@" diff --git a/lib/tests/modules/declare-flake.nix b/lib/tests/modules/declare-flake.nix new file mode 100644 index 0000000000000..d34fef3e47a00 --- /dev/null +++ b/lib/tests/modules/declare-flake.nix @@ -0,0 +1,3 @@ +{ lib, ... }: { + options.flake = lib.mkOption { type = lib.types.flake; }; +} diff --git a/lib/tests/modules/define-flake-again.nix b/lib/tests/modules/define-flake-again.nix new file mode 100644 index 0000000000000..bab2fc063dcb8 --- /dev/null +++ b/lib/tests/modules/define-flake-again.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: +let example = { + _type = "flake"; + sourceInfo = {}; + inputs = {}; + outputs = {}; + foo = lib.mkForce {}; # must be copied verbatim + packages = throw "Validating the flake is not the responsibility of this type, and evaluating any significant part of it would be too costly."; + }; +in +{ + config = { + flake = example; + }; +} diff --git a/lib/tests/modules/define-flake.nix b/lib/tests/modules/define-flake.nix new file mode 100644 index 0000000000000..08722ad8971f4 --- /dev/null +++ b/lib/tests/modules/define-flake.nix @@ -0,0 +1,17 @@ +{ config, lib, ... }: +let example = { + _type = "flake"; + sourceInfo = {}; + inputs = {}; + outputs = {}; + foo = lib.mkForce {}; # must be copied verbatim + packages = throw "Validating the flake is not the responsibility of this type, and evaluating any significant part of it would be too costly."; + }; +in +{ + options.result = lib.mkOption {}; + config = { + flake = example; + result = config.flake // { packages = null; } == example // { packages = null; }; + }; +} diff --git a/lib/types.nix b/lib/types.nix index 270ac1748c796..b40b4acb7e2a0 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -483,6 +483,18 @@ rec { merge = mergeEqualOption; }; + flake = mkOptionType { + name = "flake"; + descriptionClass = "noun"; + check = + if builtins.compareVersions builtins.nixVersion "2.12.0" >= 0 + then + x: x._type or null == "flake" + else + x: x?inputs && x?sourceInfo && x?outputs; + merge = mergeUniqueOption { message = "However, flakes can not be merged."; }; + }; + listOf = elemType: mkOptionType rec { name = "listOf"; description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";