-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
feat: Import cargo-add into cargo #10472
Conversation
r? @ehuss (rust-highfive has picked a reviewer for you, use r? to override) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! Really appreciate your great work on integrating cargo-add. I have walked through half of the PR and I think a quick feedback loop is better. Here is a summary of what I've seen so far:
Thing I like
- I love how the code is organized! It is self-contained and in a good shape.
- Every public item is documented 👍🏾
- The coverage of snapshot tests is great!
- Love the write-up of long help messages and the help headings! I can foresee lots of room to improve existing commands
Things I feel uncertain
- IIRC we have a convention that the first line of warning/error messages should be lower-case.
- Some code duplications exist. Such as the logic of selecting a package (cargo-install) or
SourceId
creation. They might be slightly different than the other but look really similar IMO. - Introduce more ways to get the job done. For example, dunce and snapbox. We need a non-trivial amount of work to migrate from one to another.
Personal preferences
- Use implicit format-arg capture when possible
- I would try to avoid making items/fields public unless they get value to do so.
- I feel like some structs do not need to derive
Clone
.
Great job and thank you @epage!
src/cargo/ops/cargo_add/mod.rs
Outdated
.max_by_key(|s| { | ||
let stable = s.version().pre.is_empty(); | ||
(stable, s.version()) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semver::Version
already handles the prerelease version as a lesser one. And I get the impression that cargo install
also ignores prerelease versions unless specified, though I cannot find the relevant code at this moment.
- https://docs.rs/semver/1.0.6/semver/struct.Version.html#total-ordering
- https://doc.rust-lang.org/1.59.0/cargo/reference/resolver.html#pre-releases
.max_by_key(|s| { | |
let stable = s.version().pre.is_empty(); | |
(stable, s.version()) | |
}) | |
.max_by_key(|s| s.version()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm reading this correctly:
semver
says1.0.0 < 2.0.0-alpha.1
cargo-add
says2.0.0-alpha.1 < 1.0.0
The goal with this code was to only select a pre-release if that is the only option available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am reading the semver crate again. It does state that pre-release versions will be ignored if not specified in VersionReq
in the doc1 and its code2. Also, each implementation of Source::query
calls Dependency::matches_id
and eventually calls VersionReq::matches
3. That being said,
- If prerelease is specified in
VersionReq
, that means the user is fine with picking prerelease version, and semver will pick them if possible. - If not, semver just ignores them.
Maybe we needn't to check prerelease here and depend on semver for consistency?
Footnotes
-
https://docs.rs/semver/1.0.6/semver/struct.VersionReq.html#associatedconstant.STAR ↩
-
https://github.com/dtolnay/semver/blob/8d8bdb2adaf4572b3c4e8502e7498838b2cf7ccb/src/eval.rs#L3-L24 ↩
-
https://github.com/rust-lang/cargo/blob/b816d82ee94acd4d6be588c0f0cd299701c55457/src/cargo/core/source/mod.rs#L32 ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can add a package with version 100000.0.0+my-package
to our tests to prevent regression. The test case install::pick_max_version
already did that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The important thing to remember is this function's intention is for us to find a version for creating a version-req. We are in the case where dep.source().is_none()
. For example, if you do a cargo add clap
on an empty crate, we should select 3.1.6 but no pre-release version. The user can explicitly do so with cargo add clap@3.0.0-rc.0
. However, if the crate only exists as pre-release versions, we should allow the user to add that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can add a package with version 100000.0.0+my-package to our tests to prevent regression. The test case install::pick_max_version already did that.
add_basic
(and many tests using the common package names) currently select from
- 0.1.1
- 0.2.3
- 0.4.1
- 20.0.0
- 999999.0.0 (no idea if I have the number of 9s right)
- 999999.0.0-alpha.1
Unsure what 100000.0.0+my-package would add that isn't already handled.
I am added a test case that is pre-release only
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! I think I got the difference. So basically cargo-add tries harder than cargo-install on finding a usable version. cargo-install avoids prerelease version by passing a STAR
VersionReq, whereas cargo-add gets a Dependency
with OptVersionReq::Any
which accepts any versions if unspecified.
Anyway, thanks for your explanation. I am fine with this tiny difference and it indeed makes cargo-add more convenient. 😄
I'll set aside SourceID as we have a specific conversation on that. As for selecting a package, I'm going to assume you mean the mapping of arguments to packages
If there is an alternative to me using As for snapbox, I think the value-add is worthwhile based on how its helped identify problems we had missed with previous testing strategies and how its sped up updating tests. I intended to explore using snapbox in cargo's tests after this and have explicitly listed it in the deferred items.
Definitely agree for newly written code but almost all of this is written before that was stablized (plus it'll be a hard habit to break until all of my crate's MSRV is new enough...). If its ok, I'll only focus do this on the side and not make it a focus of my changes.
I generally agree. Some of the cases were to avoid over-complicating things particularly when dealing with "record" types (data carrying types as compared to abstracted data structures like
For the cases of |
All comments I've marked as resolved at this point in time were handled in killercup/cargo-edit#670. I've just synced those changes back over and force-pushed. This style of development is an experiment in seeing what works for importing a command. Normally for a PR I'd do fixups to commits within the PR but since the main commits are meant to mirror specific commits from the cargo-edit repo, I do not want to touch those. Rather than applying feedback in commits on top of those, I've taken this approach. |
Just pushed a sync of killercup/cargo-edit#671 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about flooding lots of comments again. I think we are getting closer to merging!
I'll take a look at most of the test cases soon.
BTW I also found a weird case:
x = { package = "tokio", version = "1" }
# cargo add rand --rename x --optional
x = { package = "tokio", version = "1", optional = true }
It looks confusing to me not adding rand
crate but I am not sure what the expected behaviour is?
let version = manifest.package_version()?; | ||
let path = dunce::canonicalize(path)?; | ||
let available_features = manifest.features()?; | ||
Dependency::new(crate_name) | ||
.set_source(PathSource::new(path).set_version(version)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since package.version
is mandatory. Is there any use case that people add path dependency but don't want to add it's version? Below is current behaviour:
mylib = { path = "mylib" }
# cargo add ./mylib --optional
mylib = { version = "0.1.0", path = "mylib", optional = true }
And this is what I am considering:
mylib = { path = "mylib" }
# cargo add ./mylib --optional
mylib = { path = "mylib", optional = true }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could preserve the lack of version but it'll be some extra complication that, at the moment, I'm unsure if its worth. When we merge the existing dep with the CLI dep, we preserve the CLI dep's source to make sure we get the version the user specified. We'd need to specialize this based on CLI args.
let mut dependency = if let Some(mut old_dep) = old_dep.clone() {
if spec_dep.source().is_some() {
// Overwrite with `crate_spec`
old_dep.source = spec_dep.source;
}
old_dep = populate_dependency(old_dep, arg);
old_dep
} else {
spec_dep
};
We do automatically strip versions when using a dev-dependency. I've considered doing the same for crates with publish = false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this is also an edge case and there is no harm in adding the version back.
(unless it is used to prevent accidental publishing but users should definitely use publish = false
instead 😂)
Well, I'm sold. Let's skip it for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so this gets recorded, benefits for not including version
:
- In dev-dependencies, skipping
version
makes it easier for tools likecargo-release
to publish cyclic dependencies - In any dependency, skipping
version
reduces changes needed when bumping versions, automatic or manual (e.g. I have a workspace with some "cargo xtasks" that aren't published mixed with crates that are but all dependency versions have to be bumped).
I just pushed killercup/cargo-edit#672 which has everything that I marked as resolved in the most recent batch of feedback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two snapshots seem not used anymore:
- cargo_config_source_empty
- git_external
src/cargo/ops/cargo_add/mod.rs
Outdated
if spec_dep.optional.is_none() { | ||
spec_dep.optional = old_dep.optional; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only optional
is preserved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is when we switch
some-package = { package = "my-package1", version = "0.1.1", optional = true }
to
some-package = { package = "my-package2", version = "99999.0.0", optional = true }
My assumption is that the underlying crate we re-targeted to might have different features, version, registry, etc. The only thing that remains the same is how its used within the current crate, just the dependency key and optional
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I am not sure about the whole thing of overwriting an existing renamed dep with the other package. There is no clue what's going on in stderr. Do you think it is worth a warning or something else?
So far the integration looks good to me. Highlight some good parts here again:
Besides what has been mentioned in the PR description, there is an item that needs to tackle in the follow-up:
And two unrelated enhancements for cargo internals:
Anyway, I guess it's time to see if we can reach a consensus. |
@rfcbot fcp merge |
Team member @weihanglo has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
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. |
Just wanted to highlight that approving
|
As an aside
I wonder if another way of modularizing cargo is to take a "porcelain" and "plumbing" model and have the That'd also get us to dog food the command-line API. |
@rfcbot concern insta-stable We don't want to make this unstable and make it hard for people to use their existing |
Expose cargo add internals as edit API Move the manifest editing utilities from cargo add to a new `cargo::util::edit` module as part of prep work for `cargo remove` (#10520). No other substantive changes have been made, as this PR is intended only to reduce the refactoring surface area of the implementation of the feature itself. ## Background In cargo edit, there are a number of top-level modules which enable editing of Cargo manifest files (including `src/dependency.rs` and `src/manifest.rs`). In #10472, these files were added instead as a submodule of the cargo add command, with the stated intention of breaking them out later for subsequent `cargo-edit` subcommands. This PR follows through on that expectation. ## Decisions Concerns raised in #10472 regarding this change: - Where should the editing API should live? - Proposal: `cargo::ops::edit` - Justification: precedent has been set by `cargo::ops::resolve` and others to have utils shared by multiple ops live in `cargo::ops`. This is also serves to be a rather conservative API change. - Concerns: the name `edit` could be overly general for those unfamiliar with the cargo edit project (see alternatives) - Alternatives: - `cargo::edit`: this seems to me to be too top level, and would confuse users trying to discover the cargo API - `cargo::util::edit`: if we want to expose this at a higher level, perhaps renaming to act as a counterpart to `crate::util::toml` - For each of these, replace `edit` with `toml_edit`, `toml_mut`, `manifest_edit`, `manifest_mut`, `edit_toml`, `edit_manifest` etc. for a more specific module name - Any more specific naming of types reduce clashes (e.g. `Dependency` or `Manifest` being fairly generic) - Currently the only thing distinguishing these similarly named types is their path, which the `edit` module makes more clear - Alternatives: rename to `EditDependency`/`EditManifest`, `TomlDependency`/`TomlManifest`, etc.
This decision was part of rust-lang#10472 - It looks like all commands except `cargo-add` are using the prelude to define `--features`, so this should hit them all. - I've updated completions to the best of my knowledge. - It appears we don't need to update man pages or other documentation. - I'm assuming we don't add tests for every sort flag but rely on clap to have coverage to ensure it works.
In addition to `foo:1.2.3`, we now support `foo@1.2.3` for pkgids. We are also making it the default way of rendering pkgid's for the user. With cargo-add in rust-lang#10472, we've decided to only use `@` in it and to add it as an alternative to `:` in the rest of cargo. `cargo-add` originally used `@`. When preparing it for merge, I switched to `:` to be consistent with pkgids. When discussing this, it was felt `@` has precedence in too many tools to switch to `:` but that we should instead switch pkgid's to use `@`, in a backwards compatible way. See also https://internals.rust-lang.org/t/feedback-on-cargo-add-before-its-merged/16024/26?u=epage
In rust-lang#10472, cargo-add was merged with support for an inline version syntax of `foo@version`. That also served as the change proposal for extending that syntax to `cargo yank` for convinience and consistency. The major difference is that `cargo-add` is specifying a version-req while `cargo-yank` is specifying a version. This doesn't use the full `pkgid` syntax because that allows syntax that is unsupported here. This doesn't use `cargo-add`s parser because that is for version reqs.
In rust-lang#10472, cargo-add was merged with support for an inline version syntax of `foo@version`. That also served as the change proposal for extending that syntax to `cargo install` for convinience and consistency. While both commands are specifying a version-req, `cargo-install` has an implicit-but-required `=` operand while `cargo-add` allows any operand. This doesn't use the full `pkgid` syntax because that allows syntax that is unsupported here. This doesn't use `cargo-add`s parser because that is for version reqs. I held off on reusing the parser from `cargo-yank` because they had different type system needs and the level of duplication didn't seem worth it (see Rule of Three).
This decision was part of rust-lang#10472 - It looks like all commands except `cargo-add` are using the prelude to define `--features`, so this should hit them all. - I've updated completions to the best of my knowledge. - It appears we don't need to update man pages or other documentation. - I'm assuming we don't add tests for every sort flag but rely on clap to have coverage to ensure it works.
In addition to `foo:1.2.3`, we now support `foo@1.2.3` for pkgids. We are also making it the default way of rendering pkgid's for the user. With cargo-add in rust-lang#10472, we've decided to only use `@` in it and to add it as an alternative to `:` in the rest of cargo. `cargo-add` originally used `@`. When preparing it for merge, I switched to `:` to be consistent with pkgids. When discussing this, it was felt `@` has precedence in too many tools to switch to `:` but that we should instead switch pkgid's to use `@`, in a backwards compatible way. See also https://internals.rust-lang.org/t/feedback-on-cargo-add-before-its-merged/16024/26?u=epage
In rust-lang#10472, cargo-add was merged with support for an inline version syntax of `foo@version`. That also served as the change proposal for extending that syntax to `cargo yank` for convinience and consistency. The major difference is that `cargo-add` is specifying a version-req while `cargo-yank` is specifying a version. This doesn't use the full `pkgid` syntax because that allows syntax that is unsupported here. This doesn't use `cargo-add`s parser because that is for version reqs.
In rust-lang#10472, cargo-add was merged with support for an inline version syntax of `foo@version`. That also served as the change proposal for extending that syntax to `cargo install` for convinience and consistency. While both commands are specifying a version-req, `cargo-install` has an implicit-but-required `=` operand while `cargo-add` allows any operand. This doesn't use the full `pkgid` syntax because that allows syntax that is unsupported here. This doesn't use `cargo-add`s parser because that is for version reqs. I held off on reusing the parser from `cargo-yank` because they had different type system needs and the level of duplication didn't seem worth it (see Rule of Three).
Import `cargo remove` into cargo ## What does this PR try to resolve? This PR merges `cargo remove` from [cargo-edit](https://github.com/killercup/cargo-edit) into cargo. ### Motivation - General approval from community, see #5586 and #10520. - Satisfying symmetry between add and remove. - Help users clean up their manifests (for example, when users forget to remove optional dependencies from feature lists). With #10472, cargo-add was added to cargo. As part of that discussion, it was also proposed that `cargo rm` (now `cargo remove`) eventually be added as well. ### Drawbacks - Additional code always opens the door for more bugs and features - The scope of this command is fairly small though - Known bugs and most known features were resolved before this merge proposal ### Behavior `cargo remove` operates on one or more dependencies from a manifest, removing them from a specified dependencies section (using the same flags as `cargo-add`) and from `[features]` activations if the dependency is optional. Feature lists themselves are not automatically removed when made empty. Like with cargo-add, the lock file is automatically updated. Note: like `cargo add`, `cargo remove` refers to dependency names, rather than crate names, which can be different with the presence of the `name` field. Note: `cargo rm` has been renamed to `cargo remove`, based on prior art and user feedback (see [discussion](#10520)). Although this renaming is arguably an improvement, adding an `rm` alias could make the switch easier for existing users of cargo-edit (at the cost of a naming conflict which would merit insta-stabilization). #### Help output <details> ```shell $ cargo run -- remove --help cargo-remove Remove dependencies from a Cargo.toml manifest file USAGE: cargo remove [OPTIONS] <DEP_ID>... ARGS: <DEP_ID>... Dependencies to be removed OPTIONS: -p, --package [<SPEC>...] Package to remove from -v, --verbose Use verbose output (-vv very verbose/build.rs output) --manifest-path <PATH> Path to Cargo.toml --offline Run without accessing the network -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 SECTION: --dev Remove as development dependency --build Remove as build dependency --target <TARGET> Remove as dependency from the given target platform ``` </details> #### Example usage ``` cargo remove serde cargo remove criterion httpmock --dev cargo remove winhttp --target x86_64-pc-windows-gnu cargo remove --package core toml ``` ## How should we test and review this PR? This is following the pattern from cargo-add which was implemented in three different PRs (implementation, documentation, and completions), in the interest of reducing the focusing discussions in each PR and allowing cargo-add's behavior to settle to avoid documentation churn. 1. #10472 2. #10578 3. #10577 The remaining changes (documentation and shell completions) will follow shortly after. Some work has already begun on this feature in #11059. Work on this feature was carried out on the [`merge-rm`](killercup/cargo-edit@master...merge-rm) branch of cargo-edit with PRs reviewed by `@epage.` If you are interested in seeing how this feature evolved to better match cargo's internals, you might find the commit history there to be helpful. As this PR is reviewed, changes will be made both here and on that branch, with the commit history being fully maintained on the latter. `cargo remove` is structured like most other subcommands: - `src/bin/cargo/commands/remove.rs` contains the cli handling and top-level execution. - `src/cargo/ops/cargo_remove.rs` contains the implementation of the feature itself. In order to support this feature, the `remove_from_table` util was added to `util::toml_mut::manifest::LocalManifest`. Tests are split out into a separate commit to make it easier to review the production code and tests. Tests have been implemented with `snapbox`, structured similarly to the tests of `cargo add`. ### Prior art - Python: [`poetry remove`](https://python-poetry.org/docs/cli/#remove) - Supports dry run - JavaScript: [`yarn remove`](https://yarnpkg.com/cli/remove) - Supports wildcards - JavaScript: [`pnpm remove`](https://pnpm.io/cli/remove) - Go: [`go get`](https://go.dev/ref/mod#go-get) - `go get foo@none` to remove - Julia: [`pkg rm`](https://docs.julialang.org/en/v1/stdlib/Pkg/) - Supports `--all` to remove all dependencies - Ruby: [`bundle remove`](https://bundler.io/v2.2/man/bundle-remove.1.html) - Dart: [`dart pub remove`](https://dart.dev/tools/pub/cmd/pub-remove) - Supports dry run - Lua: [`luarocks remove`](https://github.com/luarocks/luarocks/wiki/remove) - Supports force remove - .NET: [`Uninstall-Package`](https://docs.microsoft.com/en-us/nuget/reference/ps-reference/ps-ref-uninstall-package) - Supports dry run - Supports removal of dependencies - Supports force remove (disregards dependencies) - Haxe: [`haxelib remove`](https://lib.haxe.org/documentation/using-haxelib/#remove) - Racket: [`raco pkg remove`](https://docs.racket-lang.org/pkg/cmdline.html#%28part._raco-pkg-remove%29) - Supports dry run - Supports force remove (disregards dependencies) - Supports demotion to weak dependency (sort of a corollary of force remove) ### Insta-stabilization In the discussion of `cargo add`'s stabilization story ([Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Stablizing.20cargo-add)), it was brought up that the feature might benefit from being insta-stabilized to avoid making the cargo-edit version of the binary hard to access. Since `cargo rm` (from cargo-edit) was renamed to `cargo remove` here, [such a conflict no longer exists](https://crates.io/search?q=cargo%20remove), so this is less of a concern. Since this feature is already has a had a long run of user testing in cargo-edit and doesn't have unsettled UI questions like cargo-add did, it might still be a candidate for insta-stabilization. ### Deferred work Necessary future work: - Add documentation. - Add shell completions. - Perform GC on workspace dependencies when they are no longer used (see #8415). - This is inspired by a feature from the RFC that was dropped (unused dependencies triggering a warning) - This was deferred out to avoid challenges with testing nightly features It was found in the review of `cargo add` that it was best to defer these first two items to focus the discussion and as there was still behavior churn during the review of cargo-add. ### Future Possibilities The following are features which we might want to add to `cargo remove` in the future: - Add a `cargo rm` alias to ease transition for current cargo-edit users - Automatically convert between dash and underscores in deps: killercup/cargo-edit#690 - Remove unused dependencies: killercup/cargo-edit#415 - Clean up caches: killercup/cargo-edit#647 ### Additional information Fixes #10520.
Import `cargo remove` into cargo ## What does this PR try to resolve? This PR merges `cargo remove` from [cargo-edit](https://github.com/killercup/cargo-edit) into cargo. ### Motivation - General approval from community, see #5586 and #10520. - Satisfying symmetry between add and remove. - Help users clean up their manifests (for example, when users forget to remove optional dependencies from feature lists). With #10472, cargo-add was added to cargo. As part of that discussion, it was also proposed that `cargo rm` (now `cargo remove`) eventually be added as well. ### Drawbacks - Additional code always opens the door for more bugs and features - The scope of this command is fairly small though - Known bugs and most known features were resolved before this merge proposal ### Behavior `cargo remove` operates on one or more dependencies from a manifest, removing them from a specified dependencies section (using the same flags as `cargo-add`) and from `[features]` activations if the dependency is optional. Feature lists themselves are not automatically removed when made empty. Like with cargo-add, the lock file is automatically updated. Note: like `cargo add`, `cargo remove` refers to dependency names, rather than crate names, which can be different with the presence of the `name` field. Note: `cargo rm` has been renamed to `cargo remove`, based on prior art and user feedback (see [discussion](#10520)). Although this renaming is arguably an improvement, adding an `rm` alias could make the switch easier for existing users of cargo-edit (at the cost of a naming conflict which would merit insta-stabilization). #### Help output <details> ```shell $ cargo run -- remove --help cargo-remove Remove dependencies from a Cargo.toml manifest file USAGE: cargo remove [OPTIONS] <DEP_ID>... ARGS: <DEP_ID>... Dependencies to be removed OPTIONS: -p, --package [<SPEC>...] Package to remove from -v, --verbose Use verbose output (-vv very verbose/build.rs output) --manifest-path <PATH> Path to Cargo.toml --offline Run without accessing the network -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 SECTION: --dev Remove as development dependency --build Remove as build dependency --target <TARGET> Remove as dependency from the given target platform ``` </details> #### Example usage ``` cargo remove serde cargo remove criterion httpmock --dev cargo remove winhttp --target x86_64-pc-windows-gnu cargo remove --package core toml ``` ## How should we test and review this PR? This is following the pattern from cargo-add which was implemented in three different PRs (implementation, documentation, and completions), in the interest of reducing the focusing discussions in each PR and allowing cargo-add's behavior to settle to avoid documentation churn. 1. #10472 2. #10578 3. #10577 The remaining changes (documentation and shell completions) will follow shortly after. Some work has already begun on this feature in #11059. Work on this feature was carried out on the [`merge-rm`](killercup/cargo-edit@master...merge-rm) branch of cargo-edit with PRs reviewed by `@epage.` If you are interested in seeing how this feature evolved to better match cargo's internals, you might find the commit history there to be helpful. As this PR is reviewed, changes will be made both here and on that branch, with the commit history being fully maintained on the latter. `cargo remove` is structured like most other subcommands: - `src/bin/cargo/commands/remove.rs` contains the cli handling and top-level execution. - `src/cargo/ops/cargo_remove.rs` contains the implementation of the feature itself. In order to support this feature, the `remove_from_table` util was added to `util::toml_mut::manifest::LocalManifest`. Tests are split out into a separate commit to make it easier to review the production code and tests. Tests have been implemented with `snapbox`, structured similarly to the tests of `cargo add`. ### Prior art - Python: [`poetry remove`](https://python-poetry.org/docs/cli/#remove) - Supports dry run - JavaScript: [`yarn remove`](https://yarnpkg.com/cli/remove) - Supports wildcards - JavaScript: [`pnpm remove`](https://pnpm.io/cli/remove) - Go: [`go get`](https://go.dev/ref/mod#go-get) - `go get foo@none` to remove - Julia: [`pkg rm`](https://docs.julialang.org/en/v1/stdlib/Pkg/) - Supports `--all` to remove all dependencies - Ruby: [`bundle remove`](https://bundler.io/v2.2/man/bundle-remove.1.html) - Dart: [`dart pub remove`](https://dart.dev/tools/pub/cmd/pub-remove) - Supports dry run - Lua: [`luarocks remove`](https://github.com/luarocks/luarocks/wiki/remove) - Supports force remove - .NET: [`Uninstall-Package`](https://docs.microsoft.com/en-us/nuget/reference/ps-reference/ps-ref-uninstall-package) - Supports dry run - Supports removal of dependencies - Supports force remove (disregards dependencies) - Haxe: [`haxelib remove`](https://lib.haxe.org/documentation/using-haxelib/#remove) - Racket: [`raco pkg remove`](https://docs.racket-lang.org/pkg/cmdline.html#%28part._raco-pkg-remove%29) - Supports dry run - Supports force remove (disregards dependencies) - Supports demotion to weak dependency (sort of a corollary of force remove) ### Insta-stabilization In the discussion of `cargo add`'s stabilization story ([Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Stablizing.20cargo-add)), it was brought up that the feature might benefit from being insta-stabilized to avoid making the cargo-edit version of the binary hard to access. Since `cargo rm` (from cargo-edit) was renamed to `cargo remove` here, [such a conflict no longer exists](https://crates.io/search?q=cargo%20remove), so this is less of a concern. Since this feature is already has a had a long run of user testing in cargo-edit and doesn't have unsettled UI questions like cargo-add did, it might still be a candidate for insta-stabilization. ### Deferred work Necessary future work: - Add documentation. - Add shell completions. - Perform GC on workspace dependencies when they are no longer used (see #8415). - This is inspired by a feature from the RFC that was dropped (unused dependencies triggering a warning) - This was deferred out to avoid challenges with testing nightly features It was found in the review of `cargo add` that it was best to defer these first two items to focus the discussion and as there was still behavior churn during the review of cargo-add. ### Future Possibilities The following are features which we might want to add to `cargo remove` in the future: - Add a `cargo rm` alias to ease transition for current cargo-edit users - Automatically convert between dash and underscores in deps: killercup/cargo-edit#690 - Remove unused dependencies: killercup/cargo-edit#415 - Clean up caches: killercup/cargo-edit#647 ### Additional information Fixes #10520.
Import `cargo remove` into cargo ## What does this PR try to resolve? This PR merges `cargo remove` from [cargo-edit](https://github.com/killercup/cargo-edit) into cargo. ### Motivation - General approval from community, see #5586 and #10520. - Satisfying symmetry between add and remove. - Help users clean up their manifests (for example, when users forget to remove optional dependencies from feature lists). With #10472, cargo-add was added to cargo. As part of that discussion, it was also proposed that `cargo rm` (now `cargo remove`) eventually be added as well. ### Drawbacks - Additional code always opens the door for more bugs and features - The scope of this command is fairly small though - Known bugs and most known features were resolved before this merge proposal ### Behavior `cargo remove` operates on one or more dependencies from a manifest, removing them from a specified dependencies section (using the same flags as `cargo-add`) and from `[features]` activations if the dependency is optional. Feature lists themselves are not automatically removed when made empty. Like with cargo-add, the lock file is automatically updated. Note: like `cargo add`, `cargo remove` refers to dependency names, rather than crate names, which can be different with the presence of the `name` field. Note: `cargo rm` has been renamed to `cargo remove`, based on prior art and user feedback (see [discussion](#10520)). Although this renaming is arguably an improvement, adding an `rm` alias could make the switch easier for existing users of cargo-edit (at the cost of a naming conflict which would merit insta-stabilization). #### Help output <details> ```shell $ cargo run -- remove --help cargo-remove Remove dependencies from a Cargo.toml manifest file USAGE: cargo remove [OPTIONS] <DEP_ID>... ARGS: <DEP_ID>... Dependencies to be removed OPTIONS: -p, --package [<SPEC>...] Package to remove from -v, --verbose Use verbose output (-vv very verbose/build.rs output) --manifest-path <PATH> Path to Cargo.toml --offline Run without accessing the network -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 SECTION: --dev Remove as development dependency --build Remove as build dependency --target <TARGET> Remove as dependency from the given target platform ``` </details> #### Example usage ``` cargo remove serde cargo remove criterion httpmock --dev cargo remove winhttp --target x86_64-pc-windows-gnu cargo remove --package core toml ``` ## How should we test and review this PR? This is following the pattern from cargo-add which was implemented in three different PRs (implementation, documentation, and completions), in the interest of reducing the focusing discussions in each PR and allowing cargo-add's behavior to settle to avoid documentation churn. 1. #10472 2. #10578 3. #10577 The remaining changes (documentation and shell completions) will follow shortly after. Some work has already begun on this feature in #11059. Work on this feature was carried out on the [`merge-rm`](killercup/cargo-edit@master...merge-rm) branch of cargo-edit with PRs reviewed by `@epage.` If you are interested in seeing how this feature evolved to better match cargo's internals, you might find the commit history there to be helpful. As this PR is reviewed, changes will be made both here and on that branch, with the commit history being fully maintained on the latter. `cargo remove` is structured like most other subcommands: - `src/bin/cargo/commands/remove.rs` contains the cli handling and top-level execution. - `src/cargo/ops/cargo_remove.rs` contains the implementation of the feature itself. In order to support this feature, the `remove_from_table` util was added to `util::toml_mut::manifest::LocalManifest`. Tests are split out into a separate commit to make it easier to review the production code and tests. Tests have been implemented with `snapbox`, structured similarly to the tests of `cargo add`. ### Prior art - Python: [`poetry remove`](https://python-poetry.org/docs/cli/#remove) - Supports dry run - JavaScript: [`yarn remove`](https://yarnpkg.com/cli/remove) - Supports wildcards - JavaScript: [`pnpm remove`](https://pnpm.io/cli/remove) - Go: [`go get`](https://go.dev/ref/mod#go-get) - `go get foo@none` to remove - Julia: [`pkg rm`](https://docs.julialang.org/en/v1/stdlib/Pkg/) - Supports `--all` to remove all dependencies - Ruby: [`bundle remove`](https://bundler.io/v2.2/man/bundle-remove.1.html) - Dart: [`dart pub remove`](https://dart.dev/tools/pub/cmd/pub-remove) - Supports dry run - Lua: [`luarocks remove`](https://github.com/luarocks/luarocks/wiki/remove) - Supports force remove - .NET: [`Uninstall-Package`](https://docs.microsoft.com/en-us/nuget/reference/ps-reference/ps-ref-uninstall-package) - Supports dry run - Supports removal of dependencies - Supports force remove (disregards dependencies) - Haxe: [`haxelib remove`](https://lib.haxe.org/documentation/using-haxelib/#remove) - Racket: [`raco pkg remove`](https://docs.racket-lang.org/pkg/cmdline.html#%28part._raco-pkg-remove%29) - Supports dry run - Supports force remove (disregards dependencies) - Supports demotion to weak dependency (sort of a corollary of force remove) ### Insta-stabilization In the discussion of `cargo add`'s stabilization story ([Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Stablizing.20cargo-add)), it was brought up that the feature might benefit from being insta-stabilized to avoid making the cargo-edit version of the binary hard to access. Since `cargo rm` (from cargo-edit) was renamed to `cargo remove` here, [such a conflict no longer exists](https://crates.io/search?q=cargo%20remove), so this is less of a concern. Since this feature is already has a had a long run of user testing in cargo-edit and doesn't have unsettled UI questions like cargo-add did, it might still be a candidate for insta-stabilization. ### Deferred work Necessary future work: - Add documentation. - Add shell completions. - Perform GC on workspace dependencies when they are no longer used (see #8415). - This is inspired by a feature from the RFC that was dropped (unused dependencies triggering a warning) - This was deferred out to avoid challenges with testing nightly features It was found in the review of `cargo add` that it was best to defer these first two items to focus the discussion and as there was still behavior churn during the review of cargo-add. ### Future Possibilities The following are features which we might want to add to `cargo remove` in the future: - Add a `cargo rm` alias to ease transition for current cargo-edit users - Automatically convert between dash and underscores in deps: killercup/cargo-edit#690 - Remove unused dependencies: killercup/cargo-edit#415 - Clean up caches: killercup/cargo-edit#647 ### Additional information Fixes #10520.
@epage, does this still hold today? I hit this error today on windows-msvc machine. By setting |
It should. Would love details on a reproduction case. |
For the symlink error, here are steps to reproduce:
Feel free to copy this to snapbox issue. Thank you! running 1 test
test cargo_add::add_basic::case ... FAILED
failures:
---- cargo_add::add_basic::case stdout ----
thread 'cargo_add::add_basic::case' panicked at 'called `Result::unwrap()` on an `Err` value: Error { inner: "Failed to copy C:\\Users\\Administrator\\Documents\\cargo\\tests\\testsuite\\cargo_add\\add_basic\\in to C:\\Users\\Administrator\\Documents\\cargo\\target\\tmp\\cit\\t0\\case\\: The system cannot find the path specified. (os error 3)", backtrace: None }', C:\Users\Administrator\Documents\cargo\crates\cargo-test-support\src\lib.rs:324:77
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace PS C:\Users\Administrator\Documents\cargo> ls .\tests\testsuite\cargo_add\add_basic\
Directory: C:\Users\Administrator\Documents\cargo\tests\testsuite\cargo_add\add_basic
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 3/8/2023 8:18 PM out
-a---- 3/8/2023 8:18 PM 15 in
-a---- 3/8/2023 8:18 PM 744 mod.rs
-a---- 3/8/2023 8:18 PM 90 stderr.log
-a---- 3/8/2023 8:18 PM 0 stdout.log
PS C:\Users\Administrator\Documents\cargo> cat .\tests\testsuite\cargo_add\add_basic\in
../add-basic.in
|
Motivation
The reasons I'm aware of are:
cargo add --help
version
andpath
fordependencies
butpath
only fordev-dependencies
(see Handle package cycles crate-ci/cargo-release#288 which led tocargo add
should "Do the Right Thing" for workspaces killercup/cargo-edit#480)Drawbacks
This is another area of consideration for new RFCs, like Stabilize Cargo's weak-dep-features and namespaced-features. rfcs#3143 (this PR supports it) or RFC: Deduplicate Cargo workspace information rfcs#2906 (implementing it will require updating
cargo-add
)This is a high UX feature that will draw a lot of attention (ie Issue influx)
e.g.
Cargo.toml
killercup/cargo-edit#521cargo add
: include only MAJOR.MINOR killercup/cargo-edit#126We'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
Example commands
For an exhaustive set of examples, see tests and associated snapshots
Particular points
--rename
)--git
and--path
only accept multiple packages from that one sourcedependencies
table is sorted and preserve that sorting when adding a new dependencycargo add serde serde_json -F serde/derive
safely if you already had a dependency onserde
but withoutserde_json
Additional decisions
Accepting the proposed
cargo-add
as-is assumes the acceptance of the following:-F
short-hand for--features
to all relevant cargo commands@
in pkgids in other commands where we accept:
<name>@<version>
in more commands, likecargo yank
andcargo install
Alternatives
:
instead of@
for versions--features
,--optional
,--no-default-features
would be position-sensitive, ie they would only apply to the crate immediate preceding them+feature
syntax (cargo add serde -F derive serde_json
)--sort
flag to sort the dependencies (existed previously)--upgrade <POLICY>
flag to choose constraint (existed previously)^
--allow-prerelease
so acargo add clap
can choose among pre-releases as wellcargo-add
cargo add serde +derive serde_json
as a shorthandPrior Art
git+
is needed for inferring git dependencies, no separate--git
flags--branch
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 throughdefaultSemverRangePrefix
in config)--cached
for using the lock file (cargo add
: Be clever and infer version from Cargo.lock killercup/cargo-edit#41)--dev
, it has--prefer-dev
which will only add the dependency if it doesn't already exist independencies
as well asdev-dependencies
--mode update-lockfile
will ensure the lock file gets updated as well@<version>
<name>[@<version>]
with path and repogithub:user/repo
--save-exact
/-E
for operators outside of the default@<version>
@none
--version
/-v
instead of--vers
(we use--vers
because of--version
/-V
)--source
instead ofpath
(path
correlates to manifest field)--git
/--branch
likecargo-add
--git-url
instead of--git
--git-ref
instead of--branch
,--tag
,--rev
Future Possibilities
--local
flagcargo add
killercup/cargo-edit#512)cargo add
: Be clever and infer version from Cargo.lock killercup/cargo-edit#41)cargo-rm
(Add cargo-rm (from cargo-edit) to cargo proper #10520),cargo-upgrade
(A method to update manifest version requirements (like cargo-edit'scargo-upgrade
) #10498), andcargo-set-version
(in that order of priority)cargo add
snippets in addition to or replacing the manifest snippetsFor 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
merge-add
branch, with only changes made to let it compile (e.g. fixing up ofuse
statements).snapbox
dev-dependency and tomod cargo_add
into the testsuitecargo-add
from direct termcolor calls to calling intoShell
Structure-wise, this is similar to other commands
bin
only defines a CLI and adapts it to anAddOptions
ops
contains a focused API with everything buried under itThe "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 usingsnapbox
, including error cases.There is additional cleanup that would ideally happen that was excluded intentionally from this PR to keep it better scoped, including
cargo
cargo::edit
?)Dependency
orManifest
being fairly generic).SourceId
creation betweencargo install
andcargo edit
snapbox
in more of cargo's testsImplementation justifications:
dunce
andpathdiff
dependencies: needed for taking paths relative to the user and make them relative to the manifest being editedindexmap
dependency (already a transitive dependency): Useful for preserving uniqueness while preserving order, like with feature valuessnapbox
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 ofcargo-test-support
so there is a larger impact when improvements are madeparse_feature
function:CliFeatures
forces items through aBTreeSet
, losing the users specified order which we wanted to preserve.Additional Information
See also the internals thread.
Fixes #5586