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
--pinned
to 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 outdated
but 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-run
to opt-out of changes or a--save
to 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 update
s behavior into cargo upgrade
and deprecate cargo update
-
cargo upgrade
would unconditionally work across the entire workspace- This will also help when supporting
[workspace.dependencies]
- This will also help when supporting
-
cargo upgrade
would modifyCargo.toml
andCargo.lock
in tandem- When changing the
Cargo.lock
file, we update the minimums in version requirements inCargo.toml
to 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 it-
cargo add
should be updated to reflect this behavior
-
- When changing the
- Replace
--aggressive
with--recursive=<true|false>
(defaulttrue
) to clarify the greater distinction being made now between direct and indirect dependencies - Update
cargo generate-lockfile
to be a more complete lockfile editor, including a--package os_str_bytes@6.3.0 6.0.0
flag for precisely controlling indirect dependencies (ascargo upgrade
can only precisely specify direct dependencies) -
cargo upgrade
s--package
and--exclude
flags would accept crate names rather than dependency names (in cases of renames) to be consistent withcargo update
-
cargo upgrade
would cause git dependencies to be updated
TBD How to select between upgrading compatible, incompatible, and pinned dependencies
-
--compatible
,--incompatible
, and--pinned
are additive but if none are specified, a default combination is selected (--compatible
or--compatible --incompatible
?) -
--compatible
,--incompatible
, and--pinned
are mutually exclusive and--compatible
is the default
Evaluation
- In effect it emulates the workflow for
-Z minimal-direct-versions
workflow so long as the user stays withincargo add
/cargo upgrade
and commits theirCargo.lock
- Ecosystem churn in adjusting workflows from
cargo update
tocargo 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
--save
flag would be added to write-through to the manifest - Suggest
cargo upgrade
is not on the latest incompatible version
cargo upgrade
would focus on incompatible upgrades and the manifest
- Only upgrade incompatible and pinned, suggesting
cargo update --save
for 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
--save
flag to update direct dependency version requirements to corresponding lockfile versions - Add
--incompatible
that forces the lockfile to upgrade across incompatible versions for unpinned version requirements- Error if this mismatches with manifest (ie
--save
is not passed in)
- Error if this mismatches with manifest (ie
- Add
--pinned
flag 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
--save
implied by--incompatible
but 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 update
already doesn't do this and would be weird to have--save
only care - This is also consistent with
cargo add
andcargo rm
- Like the above commands, the developer might iterate on what is happening and requiring
--allow-dirty
would 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 = full
is 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 update
vscargo 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