Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way for users to bulk upgrade across incompatible versions #12425

Open
5 of 11 tasks
epage opened this issue Aug 1, 2023 · 43 comments
Open
5 of 11 tasks

A way for users to bulk upgrade across incompatible versions #12425

epage opened this issue Aug 1, 2023 · 43 comments
Assignees
Labels
A-new-subcommand Area: new subcommand C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` Command-update disposition-merge FCP with intent to merge finished-final-comment-period FCP complete S-accepted Status: Issue or feature is accepted, and has a team member available to help mentor or review T-cargo Team: Cargo

Comments

@epage
Copy link
Contributor

epage commented Aug 1, 2023

Problem

A lot of incompatible upgrades have a small enough of breakages that a "upgrade all of my incompatible version requirements" would make it easier to go through this process.

This is currently exposed in the cargo upgrade command

Proposed Solution

Tasks

Deferred

  • Modifying version requirements for compatible upgrades

See #12425 (comment)

Previously, https://internals.rust-lang.org/t/feedback-on-cargo-upgrade-to-prepare-it-for-merging/17101/141

Unresolved questions

  • Flag name
    • --breaking to focus on semver (as this is limited to ^)
    • --incompatible applies to all version req operators and doesn't have a good short flag (-i is generally assumed to be "interactive")
    • Dart calls it --major-versions though we have the issue of "absolute major" (x.0.0) and "relative major (also 0.x.0, 0.0.x).
    • pnpm calls it --latest because of a "latest" tag

Notes

Related

@epage epage added A-new-subcommand Area: new subcommand Command-update C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-triage Status: This issue is waiting on initial triage. labels Aug 1, 2023
@epage
Copy link
Contributor Author

epage commented Aug 1, 2023

Below is some background on what we are using to help come up with a design

Care Abouts

Priorities

  • Don't break behavior on cargo update
  • Don't write out incompatible Cargo.lock and Cargo.toml
  • Focus is on end-users solving common problems and not on being a general programmatic CLI that is meant to cover every case
  • Be predictable and understandable
    • Can someone unfamiliar with Rust, reading a blog post, predict what different command invocations will do?
    • Preference for not having too similarly named commands
  • When higher priorities allow, avoid errors that make users go "if you know exactly what I was asking for then why didn't you just do it?"; those are a sign of issues with the UX.
  • Don't be hassle when dealing with intentionally held back dependencies

Primary use cases:

  • Want to have simple workflow for "upgrade incompatible dependencies only".
  • Want to have simple workflow for bulk lock to latest compatible (already exists as cargo update)
  • (medium priority) Selective modify one dependency's version requirement to latest compatible, latest incompatible
  • (lower priority) Want to have simple-ish workflow for bulk upgrade to latest compatible
  • (lower priority) bulk upgrade all dependencies (could just be two command invocations)

Secondary use cases are:

  • Selective modify version requirement to user-provided value
  • Upgrade explicitly pinned version requirements

Some open questions we had

  • How do we tell when a renamed dependency like tokio_03 is pinned or not?
    • We could just assume all renamed are pinned
    • We could add a dependency field but I'm a bit leery of adding that kind of bookkeeping to the manifest
    • We could force users to --exclude these dependencies but that might be a bit of a pain to always remember to do
    • We could only skip renamed if multiple dependencies exist that point to the same package

Context

Currently, cargo update is focused solely on Cargo.lock editing

  • Spans entire dependency tree
  • Multiple versions of a package may exist, referenced by name@version
  • Deals with exact versions and not version ranges
  • Only affects you and not your dependents

Version requirement editing is different in that

  • Workspace members only
  • May want differences between members
  • Supports alternative names for packages
  • Affects dependents

And as a reminder of the CLI:

cargo update -p foo  # select a non-ambiguous `foo` and update it
cargo update -p foo@ver  # selects a specific `foo` to update
cargo update -p foo --aggressive  # also update dependencies
cargo update -p foo --precise <ver>  # select a specific version
cargo update --locked  # fail if the lockfile will change

Note: cargo add --locked will also fail if the manifest will change

Some design tools we can consider include

  • Renaming a command, making the old name an alias
    • Even if there isn't a culture shift to use the new name, cargo <cmd> --help and cargo --list will point people to the new name
  • Versions without the build metadata field is a subset of version requirement syntax, we may be able to do some mixing of them
    • Precedence: using the same foo@ver syntax for versions and version requirements
  • Minimal-version resolution being the default mode would make Cargo.lock mostly align with Cargo.toml, making it easier to conflate the two commands (whether merging them or keeping separate but de-emphasizing update)

Interjection

Through this, I realized that the core of my concern with our previous attempts at a single command is that it feels like we are shoehorning new behaviors into cargo update rather than making the behavior cohesive.

  • If I see a cargo update --incompatible on a blog post, can I predict what will happen if you do cargo update? No, because --save is needed to match behavior
  • We were trying to make --package be both for package IDs and dependency names to make some of the cargo upgrade workflows work
  • We were trying to overload --precise to allow control over version requirements

I also realized that my Windows Updates vs Windows Upgrades analogy for cargo update and cargo upgrade breaks down a little because cargo upgrade can do "upgrades" that are on the level of cargo update (say we call it cargo upgrade --compatible). The difference is in the target audience (yourself vs your dependents)

@epage
Copy link
Contributor Author

epage commented Aug 1, 2023

Proposal: cargo update only changes version requirements as a side effect

The primary role of cargo update has been to update your active dependencies (ie Cargo.lock). We do not plan to change that role but give the user control to force it to update in situations that were previously unsupported, particularly updating the Cargo.toml if need be.

Behavior:

  • By default, only "safe" updates are allowed (today's behavior)
  • cargo update --incompatible / cargo update -i will force (ie update version reqs) update unpinned, incompatible versions (no other dependencies)
    • Yes, this could potentially be called --breaking or something else. The name depends on what expresses the concept clearly especially in light of any pinning behavior we have
    • Keeping -i unused would keep the door for a --interactive to be added
  • cargo update -p foo --precise ver will force the update to happen, only erroring if we can't (don't own relevant version reqs, is pinned), even if its incompatible but unpinned. Version requirement is only changed on incompatible.
    • Maybe the error on pinned could be relaxed

Somewhere between deferred and rejected (speaking for myself): Support in cargo update for writing to the manifest for non-breaking changes, like bulk compatible upgrades of version requirements (ie a -save flag) which was one of our lower priority workflows. A --save flag is more about updating versions for your dependents, which while important for having valid lower-bounds on version requirements, doesn't fit with the existing model of cargo update. Maybe in the future we can find a way to express this in cargo update that fits with how it works or maybe another command can take on this role. We just aren't wanting to distract our efforts for handling most of the use cases to handle this one. See #10498

While this tells a cohesive story, a part of me is somewhat concerned that this goes beyond the name update.

Potential related cargo update improvements

Alternatives

These are alternatives I had considered that help give an idea of what I mean by fitting into cargo.

cargo update always modifies Cargo.toml

  • This would be a breaking change
  • This would get in the way of people intentionally keeping separate versions from version requirements

We deprecate cargo update and a cargo upgrade always updates both files

  • This would get in the way of people intentionally keeping separate versions from version requirements

We migrate to minimal-version resolution by default

  • cargo update becomes less useful and we move it out of the spot light.
  • A cargo upgrade is added that is focused on editing version requirements

Separate commands for Cargo.lock (update) and Cargo.toml (upgrade)

  • Names don't clarify the role each fills
  • Much like Debian has apt dist-upgrade, maybe it could be cargo req-update?

Misc Notes

  • I don't see us making a distinction between default operator and ^ as we document them as being the same thing and sometimes people use ^ just because
  • We are erring on the side of needing cargo update && cargo update --incompatible vs cargo update --incompatible doing both as there are people who do want them separated and running two commands, while annoying, allows us to cover more use cases.

bors added a commit that referenced this issue Aug 1, 2023
fix(update): Tweak CLI behavior

### What does this PR try to resolve?

When looking at `cargo update` for #12425, I noticed that the two flags related to `--package` were not next to it or each other.  I figured grouping them like that would make things easier to browse.

When looking into that, I noticed that the two flags conflict and figured we'd provide a better error message if we did that through clap.

### How should we test and review this PR?

Looking per commit will help show the behavior changes.

### Additional information

I wanted to scope this to being simple, non-controversial, low effort, incremental improvements with this change so I did not look into the history of `--aggressive` not requiring  `--package` like `--precise` does and figure out if there is any consistency we can be working towards.
@epage epage added the T-cargo Team: Cargo label Aug 8, 2023
@epage
Copy link
Contributor Author

epage commented Aug 8, 2023

This proposal has been up here and on internals for a bit now without any major concerns raised.

@rfcbot fcp merge

@rfcbot
Copy link
Collaborator

rfcbot commented Aug 8, 2023

Team member @epage has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period An FCP proposal has started, but not yet signed off. disposition-merge FCP with intent to merge labels Aug 8, 2023
@djc
Copy link
Contributor

djc commented Aug 8, 2023

Having been fairly involved in this discussion via the internals thread, I'm happy to see this move forward in a direction that I wholeheartedly support. @epage thanks for pushing this forward!

@rfcbot rfcbot added the final-comment-period FCP — a period for last comments before action is taken label Aug 14, 2023
@rfcbot
Copy link
Collaborator

rfcbot commented Aug 14, 2023

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period An FCP proposal has started, but not yet signed off. label Aug 14, 2023
@weihanglo
Copy link
Member

Can --incompatible be used in conjunction with --package?
Just recall #11974 and not sure about the implication of cargo update --incompat -p <pkgid>.

@epage
Copy link
Contributor Author

epage commented Aug 16, 2023

Can --incompatible be used in conjunction with --package?
Just recall #11974 and not sure about the implication of cargo update --incompat -p <pkgid>.

Yes, we'd update the version requirement, if an incompatible version exists, and then run the normal code.

epage added a commit to epage/cargo that referenced this issue Aug 23, 2023
When working on cargo-upgrade, I found the meaning of `--aggressive`
confusing and named it `--recursive` there.

Renaming this in `cargo update` (with a backwards compatible alias) was
referenced in rust-lang#12425.
@MoSal
Copy link
Contributor

MoSal commented Aug 23, 2023

cargo upgrade can do "upgrades" that are on the level of cargo update (say we call it cargo upgrade --compatible). The difference is in the target audience (yourself vs your dependents)

This is probably stating the obvious. But if a dependency is imprecise, current cargo upgrade may have no effect where current cargo update would.

Let's say a dependency is serde_derive = "1" in Cargo.toml. And it's version = "1.0.183" in Cargo.lock. Then current cargo upgrade would change nothing locally, and wouldn't force dependants to get the latest version.

The separation of the tools and the clarity on each one's remit makes this, if not immediately predictable, at least easily understandable. But if this will no longer be the case, then this distinction, or a change from that behavior, should be made clear.


For what it's worth, I don't like this behavior, and I'll implement --force-precise for personal use. Especially since I only used imprecise deps in old projects.

@epage
Copy link
Contributor Author

epage commented Aug 23, 2023

FYI on zulip I brought up the idea of a pedantic machine-applicable (ie --fixable) lint to flag imprecise dependencies.

epage added a commit to epage/cargo that referenced this issue Aug 23, 2023
Generally, cargo avoids positional arguments.  Mostly for the commands
that might forward arguments to another command, like `cargo test`.
It also allows some flexibility in turning flags into options.

For `cargo add` and `cargo remove`, we decided to accept positionals
because the motivations didn't seem to apply as much (similar to `cargo
install`).

This applies the pattern to `cargo update` as well which is in the same
category of commands as `cargo add` and `cargo remove`.

Switching to a positional for `cargo update` (while keeping `-p` for
backwards compatibility) was referenced in rust-lang#12425.
epage added a commit to epage/cargo that referenced this issue Aug 23, 2023
Generally, cargo avoids positional arguments.  Mostly for the commands
that might forward arguments to another command, like `cargo test`.
It also allows some flexibility in turning flags into options.

For `cargo add` and `cargo remove`, we decided to accept positionals
because the motivations didn't seem to apply as much (similar to `cargo
install`).

This applies the pattern to `cargo update` as well which is in the same
category of commands as `cargo add` and `cargo remove`.

As for `--help` formatting, I'm mixed on whether `[SPEC]...` should be at the top like
other positionals or should be relegated to "Package selection".  I went
with the latter mostly to make it easier to visualize the less common
choice.

Switching to a positional for `cargo update` (while keeping `-p` for
backwards compatibility) was referenced in rust-lang#12425.
epage added a commit to epage/cargo that referenced this issue Aug 23, 2023
Generally, cargo avoids positional arguments.  Mostly for the commands
that might forward arguments to another command, like `cargo test`.
It also allows some flexibility in turning flags into options.

For `cargo add` and `cargo remove`, we decided to accept positionals
because the motivations didn't seem to apply as much (similar to `cargo
install`).

This applies the pattern to `cargo update` as well which is in the same
category of commands as `cargo add` and `cargo remove`.

As for `--help` formatting, I'm mixed on whether `[SPEC]...` should be at the top like
other positionals or should be relegated to "Package selection".  I went
with the latter mostly to make it easier to visualize the less common
choice.

Switching to a positional for `cargo update` (while keeping `-p` for
backwards compatibility) was referenced in rust-lang#12425.
epage added a commit to epage/cargo that referenced this issue Aug 23, 2023
When working on cargo-upgrade, I found the meaning of `--aggressive`
confusing and named it `--recursive` there.

Renaming this in `cargo update` (with a backwards compatible alias) was
referenced in rust-lang#12425.
epage added a commit to epage/cargo that referenced this issue Aug 23, 2023
Generally, cargo avoids positional arguments.  Mostly for the commands
that might forward arguments to another command, like `cargo test`.
It also allows some flexibility in turning flags into options.

For `cargo add` and `cargo remove`, we decided to accept positionals
because the motivations didn't seem to apply as much (similar to `cargo
install`).

This applies the pattern to `cargo update` as well which is in the same
category of commands as `cargo add` and `cargo remove`.

As for `--help` formatting, I'm mixed on whether `[SPEC]...` should be at the top like
other positionals or should be relegated to "Package selection".  I went
with the latter mostly to make it easier to visualize the less common
choice.

Switching to a positional for `cargo update` (while keeping `-p` for
backwards compatibility) was referenced in rust-lang#12425.
bors added a commit that referenced this issue Jun 27, 2024
@m4rch3n1ng
Copy link

would it be possible / is it currently planned to also update non-breaking versions in the Cargo.toml file?

cargo upgrade from cargo-edit currently supports that

name       old req compatible latest  new req
====       ======= ========== ======  =======
ratatui    0.26.1  0.26.1     0.27.0  0.27.0  // this is already supported in nightly
serde_json 1.0.117 1.0.118    1.0.118 1.0.118 // this is missing currently

i don't know if this is a desired feature, but since cargo upgrade already does it i am just wondering if this hasn't been discussed yet, has been discussed and has been decided against or has discussed and decided for, but just hasn't been implemented yet or if it is in another issue entirely?

@epage
Copy link
Contributor Author

epage commented Jun 28, 2024

That is covered in #12425 (comment)

Somewhere between deferred and rejected (speaking for myself): Support in cargo update for writing to the manifest for non-breaking changes, like bulk compatible upgrades of version requirements (ie a -save flag) which was one of our lower priority workflows. A --save flag is more about updating versions for your dependents, which while important for having valid lower-bounds on version requirements, doesn't fit with the existing model of cargo update. Maybe in the future we can find a way to express this in cargo update that fits with how it works or maybe another command can take on this role. We just aren't wanting to distract our efforts for handling most of the use cases to handle this one

@VorpalBlade
Copy link

These are alternatives I had considered that help give an idea of what I mean by fitting into cargo.

cargo update always modifies Cargo.toml

  • This would be a breaking change
  • This would get in the way of people intentionally keeping separate versions from version requirements

For my use case of cargo upgrade (which cargo update --breaking has been advertised as a replacement of) this would be the mode I want. I'm fine with it being an optional flag to do so, but currently cargo update --breaking is a step back functionality wise (performance is of course better though).

Quoting myself from #14204 (apparently @epage prefers feedback in this issue instead):

I was led to believe that cargo +nightly -Zunstable-options update --breaking should be a drop-in replacement for cargo upgrade -i. But it isn't. Specifically it doesn't seem to upgrade Cargo.toml unless there is a breaking change.

I use cargo upgrade to upgrade to new minor versions in Cargo.toml too. There are several reasons for this:

  • As I'm on the latest version when developing, I can't be sure I'm not relying on some new API that didn't exist in the version that is declared in Cargo.toml. While I believe there is some "min-version" flag to test with, I prefer to just force everything the latest. (No I don't care about LTS distros like Debian etc, not one bit, nor about people who are not on the most recent stable Rust. My MSRV is N.)
  • If there are any security issues in a dependency I'd also prefer to force the latest version there too.

As such it seems I will probably have to port cargo-upgrade from cargo-edit to sparse registry myself at some point. When and if I have time. As cargo update --breaking just doesn't cut it for my use case.

bors added a commit that referenced this issue Jul 22, 2024
…te-breaking, r=weihanglo

Improved error message when `update --breaking` invalid spec.

Improves an error message when trying to do `cargo update --breaking clap@foo`:

```
- [ERROR] expected a version like "1.32"
+ [ERROR] invalid package ID specification: `clap@foo`
+ Caused by:
+   expected a version like "1.32"
```

Related to #12425. Fixes an item [here](#12425 (comment)), as noted [here](#14049 (comment)).
bors added a commit that referenced this issue Jul 22, 2024
…te-breaking, r=weihanglo

Improved error message when `update --breaking` invalid spec.

Improves an error message when trying to do `cargo update --breaking clap@foo`:

```
- [ERROR] expected a version like "1.32"
+ [ERROR] invalid package ID specification: `clap@foo`
+ Caused by:
+   expected a version like "1.32"
```

Related to #12425. Fixes an item [here](#12425 (comment)), as noted [here](#14049 (comment)).
@TheLostLambda
Copy link

Personally I don't know if we took the right approach to pre-releases in #14250 — I left more of a comment on that issue (#14178 (comment)), but my overall proposal is:

  1. If version is currently stable / non-pre-release, then ignore pre-releases
  2. If version is currently a pre-release, upgrade it as normal — including upgrading to the stable version!

This, what I would consider a bug, showed up with derive-more when cargo upgrade --breaking first failed to upgrade from 1.0.0-beta.6 to 1.0.0-beta.7, and again (more egregiously) from 1.0.0-beta.7 to 1.0.0.

Let me know if I'm misguided here / if you'd like me to try to submit a PR!

@Skgland
Copy link

Skgland commented Aug 8, 2024

Personally I don't know if we took the right approach to pre-releases in #14250 — I left more of a comment on that issue (#14178 (comment)), but my overall proposal is:

  1. If version is currently stable / non-pre-release, then ignore pre-releases
  2. If version is currently a pre-release, upgrade it as normal — including upgrading to the stable version!

This, what I would consider a bug, showed up with derive-more when cargo upgrade --breaking first failed to upgrade from 1.0.0-beta.6 to 1.0.0-beta.7, and again (more egregiously) from 1.0.0-beta.7 to 1.0.0.

Let me know if I'm misguided here / if you'd like me to try to submit a PR!

Isn't this the last checkbox of #12425 (comment)?

@torhovland
Copy link
Contributor

Should it also upgrade from 1.0.0-alpha.1 to 1.0.0-beta.1? How about from 1.0.0-beta.1 to 1.0.0-pre.1?

@djc
Copy link
Contributor

djc commented Aug 8, 2024

I think all of those should be in scope for --breaking.

@ia0
Copy link

ia0 commented Aug 8, 2024

I would say yes. If you decide to go with a prerelease, it usually means you want the latest thing. Also, I remember having read somewhere that prereleases are incompatible between each other, so --breaking should bump to the latest because each version is incompatible.

@torhovland
Copy link
Contributor

But what if 1.0.0-pre.1 is older than 1.0.0-beta.1?

@djc
Copy link
Contributor

djc commented Aug 8, 2024

I mean, you're not going to have --breaking upgrade from 1.2.3 to 1.4.5 if 2.0.0 is also available, right, independent of age?

@TheLostLambda
Copy link

Should it also upgrade from 1.0.0-alpha.1 to 1.0.0-beta.1? How about from 1.0.0-beta.1 to 1.0.0-pre.1?
And
But what if 1.0.0-pre.1 is older than 1.0.0-beta.1?

I reckon we should try to stick to semver's rules here (https://semver.org/#semantic-versioning-specification-semver):

Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each dot separated identifier from left to right until a difference is found as follows:

  1. Identifiers consisting of only digits are compared numerically.

  2. Identifiers with letters or hyphens are compared lexically in ASCII sort order.

  3. Numeric identifiers always have lower precedence than non-numeric identifiers.

  4. A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.

Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.

@TheLostLambda
Copy link

And would agree with @djc that moving to a higher version is better than taking into account age — though really there should probably be a lint for anyone trying to push a lower prerelease version than is already published (if there isn't already?)

@TheLostLambda
Copy link

And just to have this all in the same place, from #14178 (comment):

I am not sure about if 2.0.0-beta.1 to 3.0.0-beta.1 should works

Perhaps that's better as 2.0.0-beta.1 to 3.0.0? That way you have to opt into each pre-release series, but you never "skip over" a chance to return to stable?

So this upgrading between pre-releases only happens when all of the pre-releases have that same MAJOR, MINOR, and PATCH versions, and otherwise it looks for a newer stable version to upgrade to?

@ia0
Copy link

ia0 commented Aug 8, 2024

Yes, I agree the logic should be:

  • Use the highest precedence release (i.e. not prerelease) that has higher precendence. If there are none, continue.
  • If the version is a prerelease, use the highest precedence prerelease that has higher precedence and is within the same release (i.e. same major, minor, and patch). If there are none, continue.
  • Keep the version as is.

(Note that even though I'm writing that, that's not what I would like to use. I would like something like #14372. But it could be a separate flag.)

@epage
Copy link
Contributor Author

epage commented Aug 8, 2024

From what I understand of the current semantics, they are what they should be.

--breaking is a "force" flag to update a dependency when a ^ operator would otherwise prevent the update.

At this time, updating through pre-releases is not a breaking change, so --breaking does not apply (see #6016, #2222, #3263). If you work around that with =, then --breaking still does not apply.

To update pre-releases that use ^, then we need the feature that is left out of this Issue, a way to bump the minimum of version reqs to what is in the lockfile (see #10498).

`"^2.0.0-beta.1" when "^2.0.0-beta.2" is available

cargo update --breaking should not change compatible version requirements

"^2.0.0-beta.1" when `"^2.0.0" is available

cargo update --breaking should not change compatible version requirements

`"^2.0.0-beta.1" when "^3.0.0-beta.1"

We need to clarify the semantics for how to handle this case

@SUPERCILEX
Copy link

SUPERCILEX commented Oct 19, 2024

@VorpalBlade I just ran into the same issue as you, seems like updating non-breaking versions (#12425 (comment)) is now being tracked here: #10498.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-new-subcommand Area: new subcommand C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` Command-update disposition-merge FCP with intent to merge finished-final-comment-period FCP complete S-accepted Status: Issue or feature is accepted, and has a team member available to help mentor or review T-cargo Team: Cargo
Projects
None yet
Development

No branches or pull requests