Skip to content
New issue

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

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

Already on GitHub? # to your account

cargo add: include only MAJOR.MINOR #126

Closed
ordian opened this issue Jun 28, 2017 · 55 comments
Closed

cargo add: include only MAJOR.MINOR #126

ordian opened this issue Jun 28, 2017 · 55 comments

Comments

@ordian
Copy link
Collaborator

ordian commented Jun 28, 2017

Add an option (or enable by default) to add a dependency with version = MAJOR.MINOR, not MAJOR.MINOR.PATCH.

@killercup
Copy link
Owner

Why?

@ordian
Copy link
Collaborator Author

ordian commented Jun 28, 2017

Why?

Why e.g. semver and regex are specified in this form in cargo-edit's Cargo.toml? :trollface:

Usually, you want to specify a minimal required feature level, so e.g. docopt uses this model.

I would rather ask why do you want to specify the full version?

@killercup
Copy link
Owner

killercup commented Jun 29, 2017 via email

@ordian
Copy link
Collaborator Author

ordian commented Jun 29, 2017

From a practical stand point, this is aesthetics, though: 1.2.3 and 1.2 and
1 will all load 1.4.9 (if that is the latest version), as cargo interprets
this as ^1.2.3 etc.

I would argue that 1.2 is better than 1 because the latter doesn't specify the minimal feature set, while the former does.

From a pessimistic stand point: 1.2.0 to 1.2.2 are probably buggy
versions—why else would they publish a 1.2.3? So, let's make sure we (and
hopefully als other dependencies) are using the absolutely latest version!

Consider the following example. At some point, you add a dependency X via cargo add, let's say 1.2.1.
Later a bug in X was found. But you forgot to update the dependency. In this scenario, 1.2.1 is worse than 1.2, because 1.2 will only compile to latest version.

@killercup
Copy link
Owner

I would argue that 1.2 is better than 1 because the latter doesn't specify the minimal feature set, while the former does.

True. But you can easily argue that 1.2.3 is better than 1.2 because the latter doesn't specify the minimal level of patches that make the needed feature work while the former does :)

In this scenario, 1.2.1 is worse than 1.2, because 1.2 will only compile to latest version.

If I understand you correctly, this is false, actually. Assuming

But you forgot to update the dependency.

means you already have the dependency version pinned in your Cargo.lock, they both act the same. And cargo update will update both to the latest version that is <2.0.0. Cargo assumes ^ default, see this for details.

@ordian
Copy link
Collaborator Author

ordian commented Jun 29, 2017

True. But you can easily argue that 1.2.3 is better than 1.2 because the latter doesn't specify the minimal level of patches that make the needed feature work while the former does :)

True, but only if you rely on the bug being fixed.

If I understand you correctly, this is false, actually.

Yeah, my brain wasn't functioning late at night. Sorry about that.

So, what you was saying in the pessimistic point is that if we have a transitive dependency X = "=1.2.0" and if we specify direct dependency X = "1.2" - it will compile, while with X = "1.2.3" it won't, forcing us to update the transitive dependency? I think, this is the rather contrived example.

In summary, I think adding an option to remove patch version won't hurt.
As well as, adding an option not to mess the whole Cargo.toml 🤣 (but that's the different issue).

@killercup
Copy link
Owner

In summary, I think adding an option to remove patch version won't hurt.

Hurt? No. But it's one more thing to maintain, and I've not yet seen a reason to bother. Are there maybe other people who also want to have this?

an option not to mess the whole Cargo.toml

No! Not an option! The default! And no --mess flag 😄 (cf. #15)

@spease
Copy link

spease commented Feb 25, 2018

I found this ticket because I've generally been only recording major.minor in my manual Cargo.toml files because I thought that was the recommended thing to do. The only case that I can see it changing the functional behavior is if somebody were to publish and then yank a version of a crate (eg if an optimization patch was applied, but then was found to introduce a security hole or regression) - in this case specifying the patch version would prevent the library from building if somebody just checked it out until a newer version of the library was published.

However it does change how it's read, since having three instead of two numbers suggests that the author of the library very specifically chose that patch number and newer, rather than simply choosing the latest patch of the major-minor numbers.

However, alternatively, you could look at the Cargo.toml as more of a log rather than a spec for what the author last built the library with. This seems more of a Cargo.lock duty though, and the rest of the information in Cargo.toml seems to generally be a as-minimal-as-possible defintion of what the library is and how it should be built.

EDIT: NB that I found this after running the cargo upgrade command, not cargo add, but I'd expect that the two commands would have the same convention (two or three version points).

@spease
Copy link

spease commented Apr 17, 2018

I ran into another issue today - using cargo upgrade more tightly constrains the dependency graph compared to using "major.minor" only. I had to step back serde and tokio_core by a version (eg 1.0.38 to 1.0.37) because of dependent libraries that this conflicted with.

@daboross
Copy link

I would definitely appreciate an option in cargo add and cargo upgrade to stick to minimal-information specification.

It's a very small wart, but I prefer to keep all of my dependency versions as 0.4 or 1 rather than specifying more detail and thus end up manually editing Cargo.toml after every use of a cargo-edit command to remove the minor and patch versions.

@YaLTeR
Copy link

YaLTeR commented Aug 16, 2018

Tried cargo upgrade for the first time today and I'm also one of these people who would prefer it to stick to 1 and 0.4 rather than major.minor.patch. 😃

@shepmaster
Copy link

I'll chime in as being in favor of cargo-edit maintaining the most specific version number possible. I'm happy that it uses the current patch level...

In fact, I'd actually love it if there was a way to walk backwards in versions to find the earliest usable / compatible version of a dependency so that my requirements are the most flexible for the people using my crate.

@YaLTeR
Copy link

YaLTeR commented Sep 30, 2018

Finding the earliest version is most certainly a bad idea because:

  • it will be missing potential bug fixes and security patches;
  • if it's a -sys crate or if it transitively depends on a -sys crate, and some other dependency of the user of your crate depends on a newer version of that -sys crate linking to a newer version of the native library, they won't be able to build their library since cargo will refuse linking in two different versions of the native library. I hit this one with openssl, one of my crate's dependencies was outdated and transitively depended on an old openssl-sys and another of my crate's dependencies had even its earliest version depending on a newer openssl-sys. I found no way out of the situation and had to drop the idea I had which involved that newer dependency.

@shepmaster
Copy link

shepmaster commented Sep 30, 2018

@YaLTeR I disagree with your assessment because it seems to not take into account Cargo's resolution logic.

If my crate A relies on libc 0.0.1 and another crate B relies on 0.0.2, a third crate C depending on both A and B will use 0.0.2 just fine. The user gets the maximal bug fixes possible.

Putting 0.0.1 in the Cargo.toml does not require that specific version will be picked, just one semver-compatible with it. Refer to the Cargo docs for the full list of operators you can use to express your crate's version constraints. For example, twox-hash is compatible with rand 0.3.10, 0.4.x, and 0.5.x because the API surface it needs hasn't changed in those versions. There's no reason for me to be incompatible with another crate that requires rand 0.4 and force the user to upgrade to 0.5.

and another of my crate's dependencies had even its earliest version depending on a newer openssl-sys

That's the problem that I'm advocating for avoiding. If that crate was compatible with both the older and newer versions and specified it in the Cargo.toml, you wouldn't have had the problem in the first place.

@YaLTeR
Copy link

YaLTeR commented Sep 30, 2018

Hmm, yeah, good point. Is there a way to force a crate with a requirement like twox-hashes to use a specific, not latest version of the depentent crate?

@shepmaster
Copy link

shepmaster commented Oct 1, 2018

a crate with a requirement like twox-hashes

In a project that uses twox-hash, and a side dependency, Cargo will pick multiple versions (for whatever reason), but you can choose to unify them:

$ cat Cargo.toml
[dependencies]
twox-hash = "1.1.1"
rand = "0.4"

$ cargo tree
ex v0.1.0 (file:///private/tmp/ex)
├── rand v0.4.3
│   └── libc v0.2.43
└── twox-hash v1.1.1
    └── rand v0.5.5
        ├── libc v0.2.43 (*)
        └── rand_core v0.2.1

$ cargo update -p rand:0.5.5 --precise 0.4.3

$ cargo tree
ex v0.1.0 (file:///private/tmp/ex)
├── rand v0.4.3
│   └── libc v0.2.43
└── twox-hash v1.1.1
    └── rand v0.4.3 (*)

@ordian ordian added the wontfix label Oct 1, 2018
@daboross
Copy link

daboross commented Oct 1, 2018

In fact, I'd actually love it if there was a way to walk backwards in versions to find the earliest usable / compatible version of a dependency so that my requirements are the most flexible for the people using my crate.

If this behavior is provided, I would be happy to not have support MAJOR.MINOR w/o patch.


Until we have that, though, I'm going to keep pushing for the option to exclude patch and possibly minor versions.

The main reason I remove the patch is that it's fairly arbitrary. 99% of the time it's just whatever patch version was the latest when I added the dependency. It usually doesn't even represent the features used, since I could easily end up depending on newer features without updating the patch version - I'd only update the actually used version in Cargo.lock. I'd never update the patch versions in any case as that creates commits and churn for no reason! Someone using cargo update on the end project will get the latest compatible version of all of my dependencies anyways.

I prefer to list the least-specific which still has semver information because further information (MINOR version if MAJOR > 0, or PATCH if MAJOR.MINOR > 0) is at best useless, and at worst outright incorrect.

@shepmaster
Copy link

I could easily end up depending on newer features without updating the patch version

I recommend adding a line to your CI to prevent this:

cargo +nightly -Z minimal-versions test

@shepmaster
Copy link

version in Cargo.lock

I don't think I've stated this explicitly, but I'm mostly concerned with libraries. For binaries it doesn't seem like like it really matters what you put in your Cargo.toml because there is a Cargo.lock.

@daboross
Copy link

daboross commented Oct 2, 2018

@shepmaster

I really should get into the habit of using minimal-versions. Just haven't updated CI configs since it became a thing. Thanks for mentioning that.

If we get that and "update to minimal version which still compiles & passes tests" then I'd be good fully abandoning this.

I was considering binary crates as well only because I follow the same principal of excluding patch versions for them, and this issue is also a problem for them. Yes, it doesn't matter as much what's in Cargo.toml, but that's even more reason to not include unnecessary and semi-arbitrary versions.

@YaLTeR
Copy link

YaLTeR commented Oct 2, 2018

Another concern I have with versions like in the twox-hash example: you're probably developing and testing the crate on the latest crate version (rand 0.5 in this example), so you can accidentally start using an API that wasn't covered by earlier versions (0.3 or 0.4) without noticing it. Does that mean you now essentially need to test on every combination of every version of a dependency like that? @shepmaster

EDIT: also why was this marked wontfix?..

@shepmaster
Copy link

you're probably developing

Yep, most likely

and testing the crate on the latest crate version

See my previous comment for one way to test this.

Does that mean you now essentially need to test on every combination of every version of a dependency like that?

Because semver is a human construct and not something mandated by the compiler, technically yes, you would always need to test with every possible version to be sure. I take a more practical approach and trust that other developers get semver right most of the time.

Note that this is already a problem you could experience. If you put itertools = "1.0" in your Cargo.toml, as suggested by this issue, then you are saying that your code works with 1.0.0, which seems more likely to be incorrect than itertools = "1.2.35". The current behaviour, of picking the current version, has the benefit that your code was tested with the version of the crate you specify at one point in time.

Most CI setups ensure that you work with the newest versions of every possible crate (since the lockfile isn't committed, you re-resolve on every run). It's possible to check for the oldest version of every possible crate via -Z minimal-versions. I don't know of any way to check every permutation 😉

@YaLTeR
Copy link

YaLTeR commented Oct 2, 2018

I take a more practical approach and trust that other developers get semver right most of the time.

Yeah, but in case of a requirement like >= 0.3, < 0.6 it's perfectly valid for, e.g., 0.5 to add some new API which you might start using without realizing it wasn't available in 0.3. I suppose -Z minimal-versions is of help here indeed.

@ghost
Copy link

ghost commented Jun 23, 2020

Wanted to follow up on this as its getting a little old to keep manually needing to remove the revision from all the dependencies in my Cargo.toml file. Are there any plans to support ignoring it and allowing just major.minor versions in this crate?

@epage
Copy link
Collaborator

epage commented Jan 26, 2022

Some quick thoughts:

  • A patch release can contain code that will break you if downgrading. Setting it to the version the user will rely on in their lock file is least likely to run into problems
  • I do not think this should be in a persistent configuration, its too situational.
  • I do not think this should be in the CLI, its too specialized
  • Configuring this well (including handle "0.x.y" releases), would be complicated
  • The user always has the workaround of specifying the version, manually adding it their manifest, or just going into the manifest and trimming it

@Kixunil
Copy link

Kixunil commented Jan 26, 2022

@kornelski ah, yeah, I had some things confused. So as long as it doesn't lead to encouraging people to bumping the versions in Cargo.toml aggressively, I think I'm fine with that approach.

@epage epage added the wontfix label Jan 26, 2022
@epage
Copy link
Collaborator

epage commented Jan 26, 2022

Going to go ahead and close this based on this discussion and my proposal on internals

@epage epage closed this as completed Jan 26, 2022
@smoelius

This comment has been minimized.

@epage

This comment has been minimized.

@smoelius

This comment has been minimized.

@epage

This comment has been minimized.

@smoelius

This comment has been minimized.

@epage

This comment has been minimized.

@smoelius

This comment has been minimized.

@epage

This comment has been minimized.

@kornelski

This comment has been minimized.

@smoelius

This comment has been minimized.

@Kixunil

This comment has been minimized.

@epage

This comment has been minimized.

@kornelski

This comment has been minimized.

@epage

This comment has been minimized.

@ordian
Copy link
Collaborator Author

ordian commented Jan 31, 2022

Getting back to the original topic, I believe something like #436 would be useful as it gives the user flexibility to choose the default style. And #217 could make it automatic by default.

@epage
Copy link
Collaborator

epage commented Jan 31, 2022

@ordian thoughts on some of the reasons given earlier against #436, like mine?

@ordian
Copy link
Collaborator Author

ordian commented Jan 31, 2022

I do not think this should be in a persistent configuration, its too situational

My assumption that people want consistent scheme for versioning, some even want =x.y.x: #436 (comment).
It's possible to specify a concrete version with format already with cargo add cc@=1.0.22. But I agree handling 0.x.y corner cases might be tricky.

But I think it's possible to implement this feature later as an experimental in cargo if there's a consensus people actually want this.

@epage
Copy link
Collaborator

epage commented Jan 31, 2022

some even want =x.y.x: #436 (comment).

For specifying the operator in cargo-add, we had --upgrade which I removed, see internals for my motivation). Some of the reasoning applies to making the operator configurable as well.

Also, I see #436 modifies cargo-upgrade though I haven't dug into that PR or the code. Does cargo-upgrade reserve the existing operator? If not, that seems like a bug to me and we should use the cargo-set-version code to preserve it.

@ordian
Copy link
Collaborator Author

ordian commented Jan 31, 2022

Does cargo-upgrade reserve the existing operator? If not, that seems like a bug to me and we should use the cargo-set-version code to preserve it.

No, we don't preserve the operator, but it's not clear what's the intended behavior, for example

a = "=1.2.3"
b = "> 1.0, < 3.0"
c = "3.0.0-alpha"
d = "0.9"

what should running cargo upgrade produce? But that's a separate topic for discussion.

bors added a commit to rust-lang/cargo that referenced this issue Apr 18, 2022
feat: Import cargo-add into cargo

### Motivation

The reasons I'm aware of are:
- Large interest, see #5586
- Make it easier to add a dependency when you don't care about the version (instead of having to find it or just using the major version if thats all you remember)
- Provide a guided experience, including
  - Catch or prevent errors earlier in the process
  - Bring the Manifest format documentation into the terminal via `cargo add --help`
  - Using `version` and `path` for `dependencies` but `path` only for `dev-dependencies` (see crate-ci/cargo-release#288 which led to killercup/cargo-edit#480)

### Drawbacks

1. This is another area of consideration for new RFCs, like rust-lang/rfcs#3143 (this PR supports it) or rust-lang/rfcs#2906 (implementing it will require updating `cargo-add`)

2. This is a high UX feature that will draw a lot of attention (ie Issue influx)

e.g.
- killercup/cargo-edit#521
- killercup/cargo-edit#126
- killercup/cargo-edit#217

We've tried to reduce the UX influx by focusing the scope to preserving semantic information (custom sort order, comments, etc) but being opinionated on syntax (style of strings, etc)

### Behavior

Help output
<details>

```console
$ cargo run -- add --help
cargo-add                                                                                                                                  [4/4594]
Add dependencies to a Cargo.toml manifest file

USAGE:
    cargo add [OPTIONS] <DEP>[`@<VERSION>]` ...
    cargo add [OPTIONS] --path <PATH> ...
    cargo add [OPTIONS] --git <URL> ...

ARGS:
    <DEP_ID>...    Reference to a package to add as a dependency

OPTIONS:
        --no-default-features     Disable the default features
        --default-features        Re-enable the default features
    -F, --features <FEATURES>     Space-separated list of features to add
        --optional                Mark the dependency as optional
    -v, --verbose                 Use verbose output (-vv very verbose/build.rs output)
        --no-optional             Mark the dependency as required
        --color <WHEN>            Coloring: auto, always, never
        --rename <NAME>           Rename the dependency
        --frozen                  Require Cargo.lock and cache are up to date
        --manifest-path <PATH>    Path to Cargo.toml
        --locked                  Require Cargo.lock is up to date
    -p, --package <SPEC>          Package to modify
        --offline                 Run without accessing the network
        --config <KEY=VALUE>      Override a configuration value (unstable)
    -q, --quiet                   Do not print cargo log messages
        --dry-run                 Don't actually write the manifest
    -Z <FLAG>                     Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for
                                  details
    -h, --help                    Print help information

SOURCE:
        --path <PATH>        Filesystem path to local crate to add
        --git <URI>          Git repository location
        --branch <BRANCH>    Git branch to download the crate from
        --tag <TAG>          Git tag to download the crate from
        --rev <REV>          Git reference to download the crate from
        --registry <NAME>    Package registry for this dependency

SECTION:
        --dev                Add as development dependency
        --build              Add as build dependency
        --target <TARGET>    Add as dependency to the given target platform

EXAMPLES:
  $ cargo add regex --build
  $ cargo add trycmd --dev
  $ cargo add --path ./crate/parser/
  $ cargo add serde serde_json -F serde/derive
```

</details>

Example commands
```rust
cargo add regex
cargo add regex serde
cargo add regex@1
cargo add regex@~1.0
cargo add --path ../dependency
```
For an exhaustive set of examples, see [tests](https://github.com/killercup/cargo-edit/blob/merge-add/crates/cargo-add/tests/testsuite/cargo_add.rs) and associated snapshots

Particular points
- Effectively there are two modes
  - Fill in any relevant field for one package
  - Add multiple packages, erroring for fields that are package-specific (`--rename`)
  - Note that `--git` and `--path` only accept multiple packages from that one source
- We infer if the `dependencies` table is sorted and preserve that sorting when adding a new dependency
- Adding a workspace dependency
  - dev-dependencies always use path
  - all other dependencies use version + path
- Behavior is idempotent, allowing you to run `cargo add serde serde_json -F serde/derive` safely if you already had a dependency on `serde` but without `serde_json`
- When a registry dependency's version req is unspecified, we'll first reuse the version req from another dependency section in the manifest.  If that doesn't exist, we'll use the latest version in the registry as the version req

### Additional decisions

Accepting the proposed `cargo-add` as-is assumes the acceptance of the following:
- Add the `-F` short-hand for `--features` to all relevant cargo commands
- Support ``@`` in pkgids in other commands where we accept `:`
- Add support for `<name>`@<version>`` in more commands, like `cargo yank` and `cargo install`

### Alternatives

- Use `:` instead of ``@`` for versions
- Flags like `--features`, `--optional`, `--no-default-features` would be position-sensitive, ie they would only apply to the crate immediate preceding them
  - This removes the dual-mode nature of the command and remove the need for the `+feature` syntax (`cargo add serde -F derive serde_json`)
  - There was concern over the rarity of position-sensitive flags in CLIs for adopting it here
- Support a `--sort` flag to sort the dependencies (existed previously)
  - To keep the scope small, we didn't want general manifest editing capabilities
- `--upgrade <POLICY>` flag to choose constraint (existed previously)
  - The flag was confusing as-is and we feel we should instead encourage people towards `^`
- `--allow-prerelease` so a `cargo add clap` can choose among pre-releases as well
  - We felt the pre-release story is too weak in cargo-generally atm for making it first class in `cargo-add`
- Offer `cargo add serde +derive serde_json` as a shorthand
- Infer path from a positional argument

### Prior Art

- *(Python)* [poetry add](https://python-poetry.org/docs/cli/#add)
  - `git+` is needed for inferring git dependencies, no separate  `--git` flags
  - git branch is specified via a URL fragment, instead of a `--branch`
- *(Javascript)* [yarn add](https://yarnpkg.com/cli/add)
  - `name@data` where data can be version, git (with fragment for branch), etc
  - `-E` / `--exact`, `-T` / `--tilde`, `-C` / `--caret` to control version requirement operator instead of `--upgrade <policy>` (also controlled through `defaultSemverRangePrefix` in config)
  - `--cached` for using the lock file (killercup/cargo-edit#41)
  - In addition to `--dev`, it has `--prefer-dev` which will only add the dependency if it doesn't already exist in `dependencies` as well as `dev-dependencies`
  - `--mode update-lockfile` will ensure the lock file gets updated as well
- *(Javascript)* [pnpm-add](https://pnpm.io/cli/add)
- *(Javascript)* npm doesn't have a native solution
  - Specify version with ``@<version>``
  - Also overloads `<name>[`@<version>]`` with path and repo
    - Supports a git host-specific protocol for shorthand, like `github:user/repo`
    - Uses fragment for git ref, seems to have some kind of special semver syntax for tags?
  - Only supports `--save-exact` / `-E` for operators outside of the default
- *(Go)* [go get](https://go.dev/ref/mod#go-get)
  - Specify version with ``@<version>``
  - Remove dependency with ``@none``
- *(Haskell)* stack doesn't seem to have a native solution
- *(Julia)* [pkg Add](https://docs.julialang.org/en/v1/stdlib/Pkg/)
- *(Ruby)* [bundle add](https://bundler.io/v2.2/man/bundle-add.1.html)
  - Uses `--version` / `-v` instead of `--vers` (we use `--vers` because of `--version` / `-V`)
  - `--source` instead of `path` (`path` correlates to manifest field)
  - Uses `--git` / `--branch` like `cargo-add`
- *(Dart)* [pub add](https://dart.dev/tools/pub/cmd/pub-add)
  - Uses `--git-url` instead of `--git`
  - Uses `--git-ref` instead of `--branch`, `--tag`, `--rev`

### Future Possibilities

- Update lock file accordingly
- Exploring the idea of a [`--local` flag](killercup/cargo-edit#590)
- Take the MSRV into account when automatically creating version req (killercup/cargo-edit#587)
- Integrate rustsec to report advisories on new dependencies (killercup/cargo-edit#512)
- Integrate with licensing to report license, block add, etc (e.g. killercup/cargo-edit#386)
- Pull version from lock file (killercup/cargo-edit#41)
- Exploring if any vendoring integration would be beneficial (currently errors)
- Upstream `cargo-rm` (#10520), `cargo-upgrade` (#10498), and `cargo-set-version` (in that order of priority)
- Update crates.io with `cargo add` snippets in addition to or replacing the manifest snippets

For more, see https://github.com/killercup/cargo-edit/issues?q=is%3Aissue+is%3Aopen+label%3Acargo-add

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

This is intentionally broken up into several commits to help reviewing
1. Import of production code from cargo-edit's `merge-add` branch, with only changes made to let it compile (e.g. fixing up of `use` statements).
2. Import of test code / snapshots.  The only changes outside of the import were to add the `snapbox` dev-dependency and to `mod cargo_add` into the testsuite
3. This extends the work in #10425 so I could add back in the color highlighting I had to remove as part of switching `cargo-add` from direct termcolor calls to calling into `Shell`

Structure-wise, this is similar to other commands
- `bin` only defines a CLI and adapts it to an `AddOptions`
- `ops` contains a focused API with everything buried under it

The "op" contains a directory, instead of just a file, because of the amount of content.  Currently, all editing code is contained in there.  Most of this will be broken out and reused when other `cargo-edit` commands are added but holding off on that for now to separate out the editing API discussions from just getting the command in.

Within the github UI, I'd recommend looking at individual commits (and the `merge-add` branch if interested), skipping commit 2.  Commit 2 would be easier to browse locally.

`cargo-add` is mostly covered by end-to-end tests written using `snapbox`, including error cases.

There is additional cleanup that would ideally happen that was excluded intentionally from this PR to keep it better scoped, including
- Consolidating environment variables for end-to-end tests of `cargo`
- Pulling out the editing API, as previously mentioned
  - Where the editing API should live (`cargo::edit`?)
  - Any more specific naming of types to reduce clashes (e.g. `Dependency` or `Manifest` being fairly generic).
- Possibly sharing `SourceId` creation between `cargo install` and `cargo edit`
- Explore using `snapbox` in more of cargo's tests

Implementation justifications:
- `dunce` and `pathdiff` dependencies: needed for taking paths relative to the user and make them relative to the manifest being edited
- `indexmap` dependency (already a transitive dependency): Useful for preserving uniqueness while preserving order, like with feature values
- `snapbox` dev-dependency: Originally it was used to make it easy to update tests as the UX changed in prep for merging but it had the added benefit of making some UX bugs easier to notice so they got fixed.  Overall, I'd like to see it become the cargo-agnostic version of `cargo-test-support` so there is a larger impact when improvements are made
- `parse_feature` function: `CliFeatures` forces items through a `BTreeSet`, losing the users specified order which we wanted to preserve.

### Additional Information

See also [the internals thread](https://internals.rust-lang.org/t/feedback-on-cargo-add-before-its-merged/16024).

Fixes #5586
# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.