pub icon indicating copy to clipboard operation
pub copied to clipboard

`dart pub upgrade` doesn't downgrade if current version is retracted, and no later versions exist.

Open sigurdm opened this issue 3 months ago • 6 comments

Originally posted in https://github.com/flutter/flutter/issues/179086

import 'package:test/test.dart';

import '../../descriptor.dart' as d;
import '../../test_pub.dart';

void main() {
  test('upgrade will downgrade if current version is retracted', () async {
    final server = await servePackages();

    server.serve('foo', '1.0.0');
    server.serve('foo', '1.5.0');

    await d.appDir(dependencies: {'foo': '^1.0.0'}).create();

    await pubGet(output: contains('+ foo 1.5.0'));
    server.retractPackageVersion('foo', '1.5.0');
    await pubUpgrade(output: '< foo 1.0.0'); // Fails, it doesn't downgrade.
  });
}

I guess the mechanism here is that the solver can see the retracted version, because it is the current version, and thus we don't downgrade.

@szakarias do you remember if this is intentional behavior?

sigurdm avatar Dec 04 '25 08:12 sigurdm

IMO this is the intended behavior.

Conceptually, retracting a version is only intended to stop new users from adopting the retracted version.

It's explicitly intended to not cause problems for people currently using the retracted version.

  • (A) This is why pub get´ will allow a _retracted version_ if said version is already in pubspec.lock` -- otherwise, people who are happily using the retracted version would be broken.
  • (B) This also why pub upgrade will allow a retracted version if said version is already in pubspec.lock -- otherwise, it's possible that people happily using the retracted version would be unable to get a version solve from pub upgrade.

One can argue that it's okay that pub upgrade doesn't work, when pub get does, but this is conceptually a bit weird.


I think fundamentally we allow existing users of a retracted version to continue using it, because we want the impact of retracting a package version to be low.

Package authors should not be worried that people will complain about broken build, when they retract a package version. We previously have one-off deleted package versions, when something really broken was accidentally published. However, in such scenarios it became really hard to determine if the damage of deleting the package version exceeds the damage of leaving it there.

Package retraction was intended to be a soft-delete, that removes the concern that existing users of the package versions are negatively affected by retraction.


I think it's fair to argue both ways for pub upgrade. I personally think that:

  • pub get working when pub upgrade might not, feels conceptually wrong.
  • We should error on the side caution and minimize the impact of retraction (so that package authors aren't afraid to retract a package version).
  • If package authors want pub upgrade to pick a version that wasn't retracted, they can just publish a newer version than the one they retracted. Giving package authors control here seems preferable to me.

That said, I can see the reasoning for the other argument.

jonasfj avatar Dec 04 '25 09:12 jonasfj

I disagree, I mainly see the current behavior as broken. And can hardly see the other side.

My mental model of retraction is that "the solver cannot see a retracted version, unless you are locked on that version" My mental model of pub upgrade is: "unlock the versions (either all or specifically those listed on the command line)"

From these two rules I would expect a retracted version to not be seen by pub upgrade.

Also from a practical perspective, I think we need a CLI invocation for "migrate away from the retracted version, even if that is lower" If pub upgrade does not do that, then we need another command for that. On the other hand I would argue that the current behavior "stay on the currently locked version despite you indicating unlocking by running pub upgrade" is almost never what you want.

Of course if you do pub upgrade bar you would not unlock foo.

pub get working when pub upgrade might not, feels conceptually wrong.

I can see this being surprising at first, but not something that troubles me after I get an explanation.

We should error on the side caution and minimize the impact of retraction (so that package authors aren't afraid to retract a package version).

I don't think you can argue that we should minimize the impact (to take that argument to the extreme, nothing should happen when you retract a package). Retraction is a quite severe action, and I think we should make appropriate consequences. I like our model that we can stay on a retracted package as long as we are locked on it, but I think it is also reasonable to unlock when asked for that. Also, why would package authors be more afraid of retracting if users get downgraded? In my mind what you want when you retract a version is for users to migrate away from that version. Our current pub upgrade semantics get in the way of them doing that.

If package authors want pub upgrade to pick a version that wasn't retracted, they can just publish a newer version than the one they retracted. Giving package authors control here seems preferable to me.

They can do that, but they cannot just do that. Publishing a new version incurs overhead, and it is non-trivial to validate that what you publish now is equivalent to the previous version. It creates a further mess in your changelog and version list. Retraction on the other hand is a fast action to take, and can easily be undone in the pub.dev ui.

sigurdm avatar Dec 04 '25 10:12 sigurdm

Weighing here as well since I posted the original bug report in the flutter repo.

A retracted package usually means that the package version has such a big problem that it's best to wipe its existence entirely.

As a user of that package I'd definitely want to know that I am using a version that the maintainer decided no one should ever use. As a matter of fact, we changed our update policy internally to fully wipe the lock file and then run pub get. This way we're sure we don't have rely on retracted version(s) for any dependencies.

--

In my opinion, the cleanest way would be:

  1. pub upgrade (and even pub get) should check for retracted versions in lock file
  2. throw error upfront and suggest to run with --ignore-retracted or --downgrade-retracted which I guess are new flags that should be added to handle this.

AlexDochioiu avatar Dec 04 '25 13:12 AlexDochioiu

(small note, pub upgrade and pub get both inform the user about retracted versions on the command line).

sigurdm avatar Dec 04 '25 13:12 sigurdm

Hmm, maybe it's not visible enough cause I didn't notice it at all. I personally would still prefer to explicitly tell what to do in this case.

AlexDochioiu avatar Dec 04 '25 13:12 AlexDochioiu

For reference the upgrade command in the test above prints:

 Resolving dependencies...
 Downloading packages...
  foo 1.5.0 (retracted)
 No dependencies changed.

We print the (retracted) in a "cyan" color:

https://github.com/dart-lang/pub/blob/639536aa78f7382b4a948669bfdc283f830b2ae9/lib/src/solver/report.dart#L542

sigurdm avatar Dec 04 '25 14:12 sigurdm