lib/deprecations: add state version default helper

Add a helper for options whose defaults change across home.stateVersion boundaries. This centralizes the warning text and documentation shape so modules do not need to hand-roll the same migration pattern at each call site.

The helper takes legacy and current branches with a runtime value plus optional static documentation text. That keeps the actual default version-gated while avoiding option docs that depend on evaluated config. Add a focused test covering the legacy warning path, the new-value path, and an explicit legacy pin that should not warn.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
This commit is contained in:
Austin Horstman 2026-03-13 12:52:38 -05:00
parent 856b01ebd1
commit 95496df8c0
3 changed files with 152 additions and 0 deletions

View file

@ -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}
'';
};
}

View file

@ -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;
}

View file

@ -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
''}
'';
};
}