diff --git a/modules/lib/deprecations.nix b/modules/lib/deprecations.nix index d04108cd..fe3833f2 100644 --- a/modules/lib/deprecations.nix +++ b/modules/lib/deprecations.nix @@ -107,4 +107,54 @@ value; in pathStr: attrs: migrate pathStr attrs; + + /* + Builds `default` and `defaultText` values for options whose defaults + change based on `home.stateVersion`, while warning users on the legacy + branch. + + Example: + let + stateVersionDefault = lib.hm.deprecations.mkStateVersionOptionDefault { + inherit (config.home) stateVersion; + since = "26.05"; + optionPath = [ "programs" "example" "foo" ]; + legacy.value = "old"; + current.value = "new"; + }; + in + lib.mkOption { + inherit (stateVersionDefault) default defaultText; + }; + */ + mkStateVersionOptionDefault = + { + stateVersion, + since, + optionPath, + legacy, + current, + extraWarning ? "", + }: + let + option = lib.showOption optionPath; + legacyText = legacy.text or (lib.generators.toPretty { } legacy.value); + currentText = current.text or (lib.generators.toPretty { } current.value); + warning = '' + The default value of `${option}` has changed from `${legacyText}` to `${currentText}`. + You are currently using the legacy default (`${legacyText}`) because `home.stateVersion` is less than "${since}". + To silence this warning and keep legacy behavior, set: + ${option} = ${legacyText}; + To adopt the new default behavior, set: + ${option} = ${currentText}; + '' + + lib.optionalString (extraWarning != "") ("\n" + extraWarning); + in + { + default = + if lib.versionAtLeast stateVersion since then current.value else lib.warn warning legacy.value; + defaultText = lib.literalExpression '' + if lib.versionAtLeast config.home.stateVersion "${since}" then ${currentText} else ${legacyText} + ''; + }; } diff --git a/tests/lib/types/default.nix b/tests/lib/types/default.nix index c9ed3031..f9ea0a1b 100644 --- a/tests/lib/types/default.nix +++ b/tests/lib/types/default.nix @@ -5,4 +5,5 @@ lib-types-either-suboptions-docs-lib = ./either-suboptions-docs-lib.nix; lib-types-gvariant-merge = ./gvariant-merge.nix; + lib-types-state-version-option-default = ./state-version-option-default.nix; } diff --git a/tests/lib/types/state-version-option-default.nix b/tests/lib/types/state-version-option-default.nix new file mode 100644 index 00000000..bd1727a4 --- /dev/null +++ b/tests/lib/types/state-version-option-default.nix @@ -0,0 +1,101 @@ +{ + config, + lib, + pkgs, + ... +}: +let + legacyDefault = lib.hm.deprecations.mkStateVersionOptionDefault { + stateVersion = "25.11"; + since = "26.05"; + optionPath = [ + "test" + "values" + "legacy" + ]; + legacy.value = "legacy"; + current.value = "new"; + }; + + newDefault = lib.hm.deprecations.mkStateVersionOptionDefault { + stateVersion = "26.05"; + since = "26.05"; + optionPath = [ + "test" + "values" + "new" + ]; + legacy.value = "legacy"; + current.value = "new"; + }; +in +{ + options.test.values = { + legacy = lib.mkOption { + type = lib.types.str; + inherit (legacyDefault) + default + defaultText + ; + }; + + new = lib.mkOption { + type = lib.types.str; + inherit (newDefault) + default + defaultText + ; + }; + + pinnedLegacy = lib.mkOption { + type = lib.types.str; + inherit (legacyDefault) + default + defaultText + ; + }; + }; + + config = { + assertions = [ + { + assertion = legacyDefault.defaultText._type == "literalExpression"; + message = "mkStateVersionOptionDefault should return a literalExpression defaultText."; + } + { + assertion = + lib.hasInfix ''config.home.stateVersion "26.05"'' legacyDefault.defaultText.text + && lib.hasInfix ''"new"'' legacyDefault.defaultText.text + && lib.hasInfix ''"legacy"'' legacyDefault.defaultText.text; + message = "mkStateVersionOptionDefault should keep defaultText as static text instead of evaluating config."; + } + ]; + + test.values.pinnedLegacy = "legacy"; + + home.file."result.txt".text = '' + legacy=${config.test.values.legacy} + new=${config.test.values.new} + pinnedLegacy=${config.test.values.pinnedLegacy} + ''; + + test.asserts.evalWarnings.expected = [ + '' + The default value of `test.values.legacy` has changed from `"legacy"` to `"new"`. + You are currently using the legacy default (`"legacy"`) because `home.stateVersion` is less than "26.05". + To silence this warning and keep legacy behavior, set: + test.values.legacy = "legacy"; + To adopt the new default behavior, set: + test.values.legacy = "new"; + '' + ]; + + nmt.script = '' + assertFileContent home-files/result.txt ${pkgs.writeText "state-version-option-default.txt" '' + legacy=legacy + new=new + pinnedLegacy=legacy + ''} + ''; + }; +}