-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(add): improve cargo add X@latest error message
#16406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
dc7d1f8
4630254
9f4e782
5dde137
f398e90
afb273a
d350679
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ use crate::util::toml_mut::dependency::WorkspaceSource; | |
| use crate::util::toml_mut::manifest::DepTable; | ||
| use crate::util::toml_mut::manifest::LocalManifest; | ||
| use crate_spec::CrateSpec; | ||
| use crate_spec::VersionSpec; | ||
|
|
||
| const MAX_FEATURE_PRINTS: usize = 30; | ||
|
|
||
|
|
@@ -349,6 +350,10 @@ fn resolve_dependency( | |
| .as_deref() | ||
| .map(CrateSpec::resolve) | ||
| .transpose()?; | ||
| let request_latest = crate_spec | ||
| .as_ref() | ||
| .is_some_and(|crate_spec| matches!(crate_spec.version(), Some(VersionSpec::Latest))); | ||
|
Comment on lines
+353
to
+355
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how come you pulled this out up here?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is used in the following if-else statement. |
||
|
|
||
| let mut selected_dep = if let Some(url) = &arg.git { | ||
| let mut src = GitSource::new(url); | ||
| if let Some(branch) = &arg.branch { | ||
|
|
@@ -362,9 +367,9 @@ fn resolve_dependency( | |
| } | ||
|
|
||
| let selected = if let Some(crate_spec) = &crate_spec { | ||
| if let Some(v) = crate_spec.version_req() { | ||
| if let Some(version) = crate_spec.version() { | ||
| // crate specifier includes a version (e.g. `docopt@0.8`) | ||
| anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{v}`)."); | ||
| anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{version}`)."); | ||
| } | ||
| let dependency = crate_spec.to_dependency()?.set_source(src); | ||
| let selected = select_package(&dependency, gctx, registry)?; | ||
|
|
@@ -399,9 +404,9 @@ fn resolve_dependency( | |
| } | ||
|
|
||
| let selected = if let Some(crate_spec) = &crate_spec { | ||
| if let Some(v) = crate_spec.version_req() { | ||
| if let Some(version) = crate_spec.version() { | ||
| // crate specifier includes a version (e.g. `docopt@0.8`) | ||
| anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`)."); | ||
| anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{version}`)."); | ||
| } | ||
| let dependency = crate_spec.to_dependency()?.set_source(src); | ||
| let selected = select_package(&dependency, gctx, registry)?; | ||
|
|
@@ -423,7 +428,14 @@ fn resolve_dependency( | |
| }; | ||
| selected | ||
| } else if let Some(crate_spec) = &crate_spec { | ||
| crate_spec.to_dependency()? | ||
| if request_latest { | ||
| // `latest` is not a dependency requirement we can write to the manifest. | ||
| // Build an unconstrained dependency and let the dedicated diagnostics below | ||
| // explain what the user should do instead. | ||
| Dependency::new(crate_spec.name()) | ||
| } else { | ||
| crate_spec.to_dependency()? | ||
| } | ||
| } else { | ||
| anyhow::bail!("dependency name is required"); | ||
| }; | ||
|
|
@@ -513,6 +525,49 @@ fn resolve_dependency( | |
| dependency = dependency.clear_version(); | ||
| } | ||
|
|
||
| // Check if user tried to use @latest and provide helpful error. | ||
| if request_latest { | ||
| // The diagnostics below compare against the resolved and latest published registry | ||
| // versions, so they only apply to registry dependencies. | ||
| if !matches!(dependency.source(), Some(Source::Registry(_))) { | ||
| anyhow::bail!("invalid version requirement `latest`"); | ||
| } | ||
|
|
||
| // Get the exact version that `cargo add <name>` would resolve to, | ||
| // respecting MSRV and existing version constraints. | ||
|
Comment on lines
+536
to
+537
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What other constraints are there?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it includes:
Basically, it's just what we would select by running the command without the
Comment on lines
+536
to
+537
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we reporting the MSRV-compatible version?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From #10741 (comment) $ cargo add clap@latest
error: invalid version requirement `latest`
help: to use X, run `cargo add clap`
help: to use the latest version, run `cargo add clap@4.5.38`I thought we usually selected X by running |
||
| let resolved = | ||
| get_latest_dependency(spec, &dependency, honor_rust_version, gctx, registry)?; | ||
| let resolved_version = resolved | ||
| .version() | ||
| .expect("resolved dependency should have version"); | ||
| // Get the actual latest non-prerelease, non-yanked version from the registry, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this comment is not entirely accurate. I discovered that the behavior of @epage Form you comment: #10741 (comment)
It may also make sense to inherit the same behavior: if there is no stable release, we suggest the pre-release version here? |
||
| // ignoring MSRV and existing version constraints. | ||
| // Only name + registry matter; `Dependency::query` ignores other fields. | ||
| let mut unconstrained_dep = Dependency::new(&dependency.name); | ||
| if let Some(registry_name) = dependency.registry() { | ||
| unconstrained_dep = unconstrained_dep.set_registry(registry_name); | ||
| } | ||
| let latest = get_latest_dependency(spec, &unconstrained_dep, Some(false), gctx, registry)?; | ||
| let latest_version = latest | ||
| .version() | ||
| .expect("latest dependency should have version"); | ||
| if resolved_version == latest_version { | ||
| anyhow::bail!( | ||
| "invalid version requirement `latest`\n\n\ | ||
| help: to add the latest version `{latest_version}`, run `cargo add {}`", | ||
| dependency.name, | ||
| ); | ||
| } else { | ||
| anyhow::bail!( | ||
| "invalid version requirement `latest`\n\n\ | ||
| help: to use `{resolved_version}`, run `cargo add {}`\n\ | ||
| help: to use the latest version, run `cargo add {}@{latest_version}`", | ||
| dependency.name, | ||
| dependency.name, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| let query = query_dependency(ws, gctx, &mut dependency)?; | ||
| let dependency = populate_available_features(dependency, &query, registry)?; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [workspace] | ||
|
|
||
| [package] | ||
| name = "cargo-list-test-fixture" | ||
| version = "0.0.0" | ||
| edition = "2015" | ||
|
|
||
| [dependencies] | ||
| my-package = "0.4" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| use crate::prelude::*; | ||
| use cargo_test_support::Project; | ||
| use cargo_test_support::compare::assert_ui; | ||
| use cargo_test_support::current_dir; | ||
| use cargo_test_support::file; | ||
| use cargo_test_support::str; | ||
|
|
||
| #[cargo_test] | ||
| fn case() { | ||
| cargo_test_support::registry::init(); | ||
| for ver in [ | ||
| "0.1.1+my-package", | ||
| "0.2.0+my-package", | ||
| "0.2.3+my-package", | ||
| "0.4.1+my-package", | ||
| "0.4.2+my-package", | ||
| "20.0.0+my-package", | ||
| "99999.0.0+my-package", | ||
| "99999.0.0-alpha.1+my-package", | ||
| ] { | ||
| cargo_test_support::registry::Package::new("my-package", ver).publish(); | ||
| } | ||
|
|
||
| let project = Project::from_template(current_dir!().join("in")); | ||
| let project_root = project.root(); | ||
| let cwd = &project_root; | ||
|
|
||
| snapbox::cmd::Command::cargo_ui() | ||
| .arg("add") | ||
| .arg_line("my-package@latest") | ||
| .current_dir(cwd) | ||
| .assert() | ||
| .failure() | ||
| .stdout_eq(str![""]) | ||
| .stderr_eq(file!["stderr.term.svg"]); | ||
|
|
||
| assert_ui().subset_matches(current_dir!().join("out"), &project_root); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [workspace] | ||
|
|
||
| [package] | ||
| name = "cargo-list-test-fixture" | ||
| version = "0.0.0" | ||
| edition = "2015" | ||
|
|
||
| [dependencies] | ||
| my-package = "0.4" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [workspace] | ||
|
|
||
| [package] | ||
| name = "cargo-list-test-fixture" | ||
| version = "0.0.0" | ||
| edition = "2015" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| use crate::prelude::*; | ||
| use cargo_test_support::Project; | ||
| use cargo_test_support::compare::assert_ui; | ||
| use cargo_test_support::current_dir; | ||
| use cargo_test_support::file; | ||
| use cargo_test_support::str; | ||
|
|
||
| #[cargo_test] | ||
| fn case() { | ||
| cargo_test_support::registry::alt_init(); | ||
| for ver in [ | ||
| "0.1.1+my-package", | ||
| "0.2.0+my-package", | ||
| "0.2.3+my-package", | ||
| "0.4.1+my-package", | ||
| "0.4.2+my-package", | ||
| "20.0.0+my-package", | ||
| "99999.0.0+my-package", | ||
| "99999.0.0-alpha.1+my-package", | ||
| ] { | ||
| cargo_test_support::registry::Package::new("my-package", ver) | ||
| .alternative(true) | ||
| .publish(); | ||
| } | ||
|
|
||
| let project = Project::from_template(current_dir!().join("in")); | ||
| let project_root = project.root(); | ||
| let cwd = &project_root; | ||
|
|
||
| snapbox::cmd::Command::cargo_ui() | ||
| .arg("add") | ||
| .arg_line("my-package@latest --registry alternative") | ||
| .current_dir(cwd) | ||
| .assert() | ||
| .failure() | ||
| .stdout_eq(str![""]) | ||
| .stderr_eq(file!["stderr.term.svg"]); | ||
|
|
||
| assert_ui().subset_matches(current_dir!().join("out"), &project_root); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [workspace] | ||
|
|
||
| [package] | ||
| name = "cargo-list-test-fixture" | ||
| version = "0.0.0" | ||
| edition = "2015" |
Uh oh!
There was an error while loading. Please reload this page.