diff --git a/lib/deprecation.nix b/lib/deprecation.nix index 9c724dd0..f462fb55 100644 --- a/lib/deprecation.nix +++ b/lib/deprecation.nix @@ -129,23 +129,62 @@ rec { packageName, oldPackageName ? packageName, }: - { options, ... }: + { config, options, ... }: let - oldOptionPath = builtins.concatMap lib.toList [ + cfg = config.dependencies.${packageName}; + opt = options.dependencies.${packageName}; + oldOptPath = builtins.concatMap lib.toList [ "plugins" plugin "${oldPackageName}Package" ]; - depOption = options.dependencies.${packageName}; - depOptionLoc = lib.dropEnd 1 depOption.enable.loc; + oldOpt = lib.getAttrFromPath oldOptPath options; - instructions = '' - Please use the `${lib.showOption depOptionLoc}` top-level option instead: - - `${depOption.enable} = false` to disable installing `${packageName}` - - `${depOption.package}` to choose which package to install for `${packageName}`. - ''; + # We can't use `oldOpt.value` because that will use our `apply`, so we merge the definitions ourselves: + oldDef = lib.modules.mergeDefinitions oldOpt.loc oldOpt.type oldOpt.definitionsWithLocations; + + # Conceptually similar to `mkDerivedConfig`, but uses our manually merged definition + configFromOldDef = + { + predicate ? _: true, + apply ? lib.id, + }: + let + inherit (oldDef) mergedValue isDefined; + inherit (oldDef.defsFinal') highestPrio; + condition = isDefined && predicate mergedValue; + value = apply mergedValue; + in + lib.mkIf condition (lib.mkOverride highestPrio value); in { - imports = [ (lib.mkRemovedOptionModule oldOptionPath instructions) ]; + options = lib.setAttrByPath oldOptPath ( + lib.mkOption { + type = with lib.types; nullOr package; + description = "Alias of `${opt.enable}` and `${opt.package}`."; + visible = false; + apply = + let + value = if cfg.enable then cfg.package else null; + use = builtins.trace "Obsolete option `${oldOpt}' is used. It was replaced by `${opt.enable}' and `${opt.package}'."; + in + _: use value; + } + ); + + config = lib.mkIf oldOpt.isDefined { + warnings = [ + "The option `${oldOpt}' defined in ${lib.showFiles oldOpt.files} has been replaced by `${opt.enable}' and `${opt.package}'." + ]; + + dependencies.${packageName} = { + enable = configFromOldDef { + apply = pkg: pkg != null; + }; + package = configFromOldDef { + predicate = pkg: pkg != null; + }; + }; + }; }; } diff --git a/tests/test-sources/modules/dependencies.nix b/tests/test-sources/modules/dependencies.nix index b688c29a..b2e3de92 100644 --- a/tests/test-sources/modules/dependencies.nix +++ b/tests/test-sources/modules/dependencies.nix @@ -47,4 +47,48 @@ )) ]; }; + + # Integration test for `lib.nixvim.deprecation.mkRemovedPackageOptionModule` + removed-package-options = + { + lib, + pkgs, + config, + ... + }: + { + test = { + buildNixvim = false; + warnings = expect: [ + (expect "count" 2) + + (expect "any" "The option `plugins.chatgpt.curlPackage' defined in `") + (expect "any" "has been replaced by `dependencies.curl.enable' and `dependencies.curl.package'.") + + (expect "any" "The option `plugins.glow.glowPackage' defined in `") + ]; + }; + + plugins.chatgpt.curlPackage = null; + plugins.glow.glowPackage = pkgs.hello; + + assertions = [ + { + assertion = !lib.elem pkgs.curl config.extraPackages; + message = "Expected curl not to be installed."; + } + { + assertion = config.dependencies.glow.enable; + message = "Expected `dependencies.glow` to be enabled."; + } + { + assertion = lib.elem pkgs.hello config.extraPackages; + message = "Expected hello to be installed."; + } + { + assertion = !lib.elem pkgs.glow config.extraPackages; + message = "Expected glow not to be installed."; + } + ]; + }; }