Like with cargo-add, I'm interested in merging cargo upgrade into cargo. There are some open questions on the design and, with how successful the cargo-add thread was, I'm hopeful we can have a productive brainstorming session on resolving them to finalize the design for merging into cargo. I especially want to call attention to the first point of concern below as its the most critical, undefined, and yet could have the biggest repercussions.
Background
cargo upgrade performs bulk edits to a Cargo.toml's version requirements.
Example run on cargo
Notes
- It preserves existing version requirement precision, where possible
- By default it ignores dependencies that are most likely pinned, requiring
--pinnedto upgrade them also. Besides the version requirement itself (e.g. using=), we assume renamed dependencies are pinned dependencies since a common use case is to have multiple incompatible versions of a dependency - Output is formatted into a table modeled after
cargo outdatedbut summarizes "uninteresting" cases. The goal is to (1) build trust that its working by communicating why we made a choice, (2) show information for users to make decisions, while (3) not overwhelming the user especially in large crates or workspaces
User Care-Abouts
- Separate
cargo update(for lock file) andcargo upgrade(for manifest) commands is confusing to some users - Some application authors want to upgrade to get the latest bug fixes and features
- Some library authors want to keep up on breaking changes but otherwise want to keep version requirements low to allow dependents to choose the version right for them (lower audit churn with fewer upgrades, workaround bugs, MSRV, etc)
- Frequently, users will want to defer certain breaking changes because of the level of churn or missing functionality
- Users need to be able to force a specific minimum version (and test with it) for MSRV or another need
- Crate authors want their manifest to match their lockfile to ensure that they do not accidentally use new functionality that breaks their users building with an old version.
- minimal (direct?) versions is another tool in solving this
- Stablizing
#[stable]attributes and warning when an API item is newer than the minimal version is being used is another tool for solving this problem
- Crate authors want to be able to test out their changes (either a
--dry-runto opt-out of changes or a--saveto opt-in) - Crate authors want to script checking if there are any breaking changes (by checking the exit code)
- Sometimes crate authors need to constrain (or pin) a dependency to workaround a problem and need to specifically decide when to modify the extra constraints
- Sometimes crate authors need to rename a dependency to access an old version and a new version at the same time and don't want to upgrade the old version to the new version, negating its purpose
- Some crate authors specifically chose their version requirement precision while authors want to ensure they use full precision always
- Some maintainers would like to have isolated PRs for semver-compatible and semver-breaking upgrades as semver-breaking upgrades deserve a bit more scrutiny (is the crate exposed in the API making the upgrade a breaking release? were there behavior changes that might not have been caught by tests?)
Proposal
Merge cargo updates behavior into cargo upgrade and deprecate cargo update
cargo upgradewould unconditionally work across the entire workspace- This will also help when supporting
[workspace.dependencies]
- This will also help when supporting
cargo upgradewould modifyCargo.tomlandCargo.lockin tandem- When changing the
Cargo.lockfile, we update the minimums in version requirements inCargo.tomlto match - If a user is precise on their desired upgrade (
cargo upgrade -p clap@^3.2.5), the lock file will reflect that version and not aggressively upgrade itcargo addshould be updated to reflect this behavior
- When changing the
- Replace
--aggressivewith--recursive=<true|false>(defaulttrue) to clarify the greater distinction being made now between direct and indirect dependencies - Update
cargo generate-lockfileto be a more complete lockfile editor, including a--package os_str_bytes@6.3.0 6.0.0flag for precisely controlling indirect dependencies (ascargo upgradecan only precisely specify direct dependencies) cargo upgrades--packageand--excludeflags would accept crate names rather than dependency names (in cases of renames) to be consistent withcargo updatecargo upgradewould cause git dependencies to be updated
TBD How to select between upgrading compatible, incompatible, and pinned dependencies
--compatible,--incompatible, and--pinnedare additive but if none are specified, a default combination is selected (--compatibleor--compatible --incompatible?)--compatible,--incompatible, and--pinnedare mutually exclusive and--compatibleis the default
Evaluation
- In effect it emulates the workflow for
-Z minimal-direct-versionsworkflow so long as the user stays withincargo add/cargo upgradeand commits theirCargo.lock - Ecosystem churn in adjusting workflows from
cargo updatetocargo upgrade - Potential complications from the interactions from locking direct and indirect dependencies making it harder for the version requirement to reflect what is selected
Alternatives
Keep cargo upgrade and cargo update separate
cargo update would focus on the lockfile and compatible upgrades
- A
--saveflag would be added to write-through to the manifest - Suggest
cargo upgradeis not on the latest incompatible version
cargo upgrade would focus on incompatible upgrades and the manifest
- Only upgrade incompatible and pinned, suggesting
cargo update --savefor compatible upgrades
Evaluation
- Maintains user confusion over role of the two commands
- Maintains status quo wrt
-Z minimal-direct-versions
Merge cargo upgrade into cargo update
- Add
--saveflag to update direct dependency version requirements to corresponding lockfile versions - Add
--incompatiblethat forces the lockfile to upgrade across incompatible versions for unpinned version requirements- Error if this mismatches with manifest (ie
--saveis not passed in)
- Error if this mismatches with manifest (ie
- Add
--pinnedflag to update pinned version requirements to incompatible versions - Provide summary table at the end for direct dependencies
TBD How to select between upgrading compatible, incompatible, and pinned dependencies without breaking compatibility
Evaluation
- Extra ceremony to upgrade
cargo update --incompatible --save- Some suggested to make
--saveimplied by--incompatiblebut then the user sometimes needs it, sometimes doesn't
- Some suggested to make
- No way to specify precise version requirements
- So long as the user always passes in
--save, this emulates the workflow for-Z minimal-direct-versions
cargo upgrade (any variant) does not modify the repo by default
cargo updatealready doesn't do this and would be weird to have--saveonly care- This is also consistent with
cargo addandcargo rm - Like the above commands, the developer might iterate on what is happening and requiring
--allow-dirtywould be disruptive
Explicit configuration over a dependencies upgrade policy
Currently, renamed and overly constrained dependencies are considered pinned and require opt-ing in to upgrade.
In addition or as an alternative to those assumptions, we could add manifest depednency fields or cargo config fields to control the upgrade policy for a dependency
e.g. even just na = { package="nalgebra", version="0.31.1", pinned=false }, or package.update.precision = "full", or lots of other knobs which could be persistent in the manifest rather than ephemeral CLI flags.
Evaluation
- Users can ensure exactly the policy they want
- More features means more documentation for users to go through and more cognitive overhead in using manifests
- So long as users use
cargo add,precision = fullis unneeded - "pinned" is redundant with a lot of version req syntax and doesn't tell the tool "how pinned" it is (what are safe compatible upgrades)
Prior Art
Prior art
- (Python) poetry add
- Upgrade individual dependency via
poetry add <name>@latest - No bulk upgrade, see Suggestion: new command to bump versions of dependencies in `pyproject.toml` · Issue #461 · python-poetry/poetry · GitHub
- Upgrade individual dependency via
- (Javascript) yarn/pnpm ?
- Unclear how much behavior is like
cargo updatevscargo upgrade
- Unclear how much behavior is like
- (Go)
go get- Upgrade individual dependency via
go get <dep>@latest - No bulk upgrade
- Upgrade individual dependency via
- ... Julia, Ruby, or Dart: ?
- Couldn't find any equivalents
Status Updates
(As of 8/1/2022)
- Updated proposal to merge cargo-update / cargo-upgrade
