# Notes
- epage: intentionally keep version requirements low
- Minimize dependency churn
- Allow final program to decide versions
- Want to verify version requirements
- Want version req validation in developer workflow
- Feels free to upgrade dev dependencies (within MSRV)
Aside:
- Should we switch `--aggressive` to `--recursive` (and keep a hidden alias)
- Should we have `cargo install` fuzzy matching to `cargo update`
# Requirements
## Initial
Common (Primary)
- Can't break current default `update` behavior.
- Full behavior is targeted at interactive user for common use cases; not intended to handle every possible use case
- Avoid modifying intentionally held back versions
- Explicitly pinned like `tokio = "=0.3"`
- Never want to end up in a scenario where the lockfile and the manifest are incompatible (e.g. we should never write versions to the lockfile that the manifest's dependencies wouldn't permit).
- Be predictable, 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.
- 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) (could just be two commands) bulk upgrade all dependencies
Common (Secondary)
- Use cases
- Selective modify version requirement to user-provided
- Upgrade explicitly pinned
- `cargo update -p foo@ver`
- Proposal: manifest editing would start out with the same behavior we already have for lockfile editing, using pkgid with exact version matches (0.3.0 matches 0.3.0, not 0.3.5, and you can't write just 0.3), consider fuzzy shorthand in the future but not in the initial version
- Selection examples:
- upgrade tokio 0.2 to 0.3 but leave tokio 1.0 alone
- upgrade tokio 0.3 to 1.0 but leave tokio 0.3.1 alone
Upgrade metadata
- If it helps make things more automatic to distinguish between `tokio03` (using old major version) versus `foo = { package = "foo-compat", version = "..." }` (which should be upgraded)
- `pinned` metadata
- Could be hacky and if **any** req is used, even `^`
- skip by default to leave room for it to be added later?
- To make things easier by default for initial releases ~~to avoid a pile of complaints to cargo team~~
- Could print a message ("1 renamed dependency not upgraded", and if we make `-p` work, we could add "use -p to upgrade")
- `--exclude` but its annoying to include every time
- If workflows are combined, `--exclude` might get complicated for resolver or workflow
- `cargo update -p` could force them to be upgraded
- If trivial
- Could do today with pkgid, annoying
- Print the command needed if `--verbose`?
- Only skip rename if multiple exist
- Too clever?
- Do a scan on crates.io to check
- Will people get confused?
Ambiguous `cargo update -p`
- Error, reports versions to select
Josh
Ed Page
- Be predictable, understandable
- Preference against inferred arguments that dramatically change the behavior
- User operations:
- Selective locking to a specific version (exists as `cargo update -p <name> --precise <ver>`)
- (everything under "common")
- Users may want to hold back (lock or upgrade) build+normal dependencies to reduce end-user churn but no similar motivation exists for dev dependencies and upgrading them is a plus to allow the developer workflow to deal with fewer bugs and have latest features
Open questions
- How do we tell when a renamed dependency like `tokio_03` is pinned or not?
- What should the lockfile represent? What does a change to the lockfile mean for the manifest or manifest to the lockfile?
- Possibilities:
- Golden stack / last known good configuration
- Test state (e.g. minimal, latest, MSRV compatible)
- Lowest common denominator for development
- Latest bug fixes and features to include when distributing a binary
- Mechanically, it is currently "maximal for what last changed"
Note: "compatible" and "incompatible" are relative to the version requirements and not semver (though thats the default version requirement operator)
## Models of Operation
Version lock editing
- Spans entire dependency tree
- Multiple versions of a package, referenced by name@version
- Only affects you
Version requirement editing
- Workspace members only
- Multiple compatible and incompatible version requirements of a package
- Affects dependents
## `cargo update` compatibility
```console=
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
```
- `cargo add --locked` will also fail if the manifest will change
# Design
Resources
- 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 w/ metadata 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`)
## Proposals
### (exclusively) update command
The primary role of `cargo update` is to update your active dependencies.
By default, only "safe" updates are allowed
`cargo update --incompatible` / `cargo update -i` will force (ie update version reqs) exclusively update unpinned, incompatible versions
`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.
Anti-goals: general editing of manifests. There will never be a `--save` flag or any similar functionality for getting a `Cargo.toml` in sync with `Cargo.lock`. Maybe a `cargo lockfile` command could sits between `cargo generate-lockfile` and `cargo update` that supports synchronizing with the manifest.
### separate `Cargo.lock` and `Cargo.toml` commands
`cargo update` is the same as today
`cargo upgrade` will update unpinned incompatible version requirements. `--compatible` will update compatible version requirements to latest.
This divide somewhat mirrors automatic Windows Updates vs Windows Upgrades nomenclature.
### Minimal versions
We migrate to minimal versions resolver by default.
`cargo update` becomes less useful and we move it out of the spot light.
`cargo upgrade` becomes focused on editing version requirements
# Appendix: RustNL Notes
Looking to summarize a recent discussion on where to go from here. I've tried to go back and include some details from the thread but I likely missed some.
Expected user operations across `cargo update` and `cargo upgrade`
- Selective upgrade to latest compatible, latest incompatible, or user-provided version req
- Selective locking to a specific version
- Bulk upgrade to latest compatible
- Bulk upgrade to latest incompatible
- Selectively ignoring renamed dependencies as they are likely meant to be used with a specific major version (e.g. `tokio_03`)
- Selectively ignoring non-default version requirement operators as the user might have intended to pin things
Note: "compatible" and "incompatible" are relative to the version requirements and not semver (though thats the default version requirement operator)
`cargo update` today
- Works at workspace level
- Edits `Cargo.lock`, not `Cargo.toml`
- `cargo update -p foo@ver`, `@ver` is used to disambiguate `foo` within the dependency tree (must be full version)
- `--precise ver` takes a full version for replacing `ver` in the lockfile and must remain compatible
- `--aggresive` to recursively update `foo`
Considerations:
- What do we optimize for (ie default)? For *some* workflows:
- `[[bin]]` maintainers may want bulk updates of everything, most likely with a focus on compatible as incompatible might need hand updates
- `[lib]` normal/build deps: maintainers may want compatible updates by hand. incompatible bulk updates are good for checking of it works but sometimes they will need to be done by hand
- `[lib]` dev-only deps: like `[[bin]]`
- Upcoming [MSRV-aware resolver](https://github.com/rust-lang/cargo/issues/5657) (personally leaning towards this always being enabled)
- [`-Zdirect-minimal-versions`](https://github.com/rust-lang/cargo/issues/5657)
- Users may want to keep their lockfile and requirements in-sync so what gets tested locally is at least what your dependents will use
- This either requires a sticky `-Zdirect-minimal-versions` or a way to update requirements without updating the lockfile
Proposal 1: Deprecate `cargo update` in favor of `cargo upgrade`
- `cargo upgrade` (formerly `cargo update && cargo upgrade --to-lockfile`)
- `cargo upgrade --incompatible` / `cargo upgrade -i`
- Change version requirements to latest incompatible, leaving compatible versions the same
- Non-recursively updates lock file
- Ignores pinned dependencies
- `cargo upgrade -p foo`
- Change dep names "foo" (not dependency package names)
- Can be used with `-i`, `--pinned`
- `cargo upgrade -p foo@verreq`
- Change dep names "foo" to the specified version req
- **Open question:** is `--pinned` needed?
- **Open question:** what command handles the role of `cargo update -p foo --precise ver`?
Proposal 2: Separate Commands
- `cargo upgrade` only does incompatible (formerly `cargo upgrade --incompatible allow --compatible ignore`)
- `cargo update --save` does compatible
Proposal 3: Merge Upgrade into Update
- `cargo update` stays the same except...
- `cargo update --save` (formerly `cargo upgrade --incompatible false --compatible true`)
- **Concern:** Inconsistency on whether `--save` is needed feels off to me
- `cargo update --incompatible` (formerly `cargo upgrade --incompatible true --compatible false`)
- `cargo update -p tokio_03` de-sugars the dep name to package name + version (`tokio@0.3.12`)
- `cargo update -p foo --precise ver`
- **Open question** do we make a version requirement out of `--precise` and not allow controlling the precision or operators, do we hack up `--precise`s behavior, or find a new flag?
- **Open question** if `ver` is incompatible, do your need `--save`, `-i`, or either?
- **Open question** can add support for `cargo update --precise --save` to fully specify all version requirements
- `cargo update && cargo update --save --locked` would be an error, mirroring `cargo add`s behavior which has `--locked` apply to the manifest as well
Note:
- All of these switch from being able to upgrade both compatible and incompatible in a single command to requiring two invocations. We are assuming that doing both is a low enough occurrence that the simplified set of flags for the more common cases justifies it
- We have not addressed users limiting actions to normal+build vs dev
- Cargo tree does this through the `--edges` flag which works when talking about graphs but not really in this context
- Can we correctly limit upgrades that would cause incompatible `links`?
- See https://github.com/killercup/cargo-edit/issues/844
- Personally, just ran into a failure with this with `git2`
- Do we bother allowing upgrading of pinned dependencies? Leaning towards "no"
- When only upgrading incompatible version requirements, if that forces a direct dependency to do a compatible upgrade, should we also update the version requirements?
I think the next step is updating cargo-edit to one of the proposals for us to gain first-hand experience with how it works out
- (1) and (2) would be easy to implement for just getting something better into people's hands
- (3) would be the most different in workflow and I suspect the one that we would learn the most from people using it even if I personally hope we don't go this route