Skip to content

improve error message for unmatched prerelease dependencies #7191

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

Merged
merged 2 commits into from
Aug 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 101 additions & 86 deletions src/cargo/core/resolver/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,100 +203,115 @@ pub(super) fn activation_error(
};
candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));

let mut msg = if !candidates.is_empty() {
let versions = {
let mut versions = candidates
.iter()
.take(3)
.map(|cand| cand.version().to_string())
.collect::<Vec<_>>();
let mut msg =
if !candidates.is_empty() {
let versions = {
let mut versions = candidates
.iter()
.take(3)
.map(|cand| cand.version().to_string())
.collect::<Vec<_>>();

if candidates.len() > 3 {
versions.push("...".into());
}

versions.join(", ")
};

let mut msg = format!(
"failed to select a version for the requirement `{} = \"{}\"`\n \
candidate versions found which didn't match: {}\n \
location searched: {}\n",
dep.package_name(),
dep.version_req(),
versions,
registry.describe_source(dep.source_id()),
);
msg.push_str("required by ");
msg.push_str(&describe_path(
&cx.parents.path_to_bottom(&parent.package_id()),
));

if candidates.len() > 3 {
versions.push("...".into());
// If we have a path dependency with a locked version, then this may
// indicate that we updated a sub-package and forgot to run `cargo
// update`. In this case try to print a helpful error!
if dep.source_id().is_path() && dep.version_req().to_string().starts_with('=') {
msg.push_str(
"\nconsider running `cargo update` to update \
a path dependency's locked version",
);
}

versions.join(", ")
};

let mut msg = format!(
"failed to select a version for the requirement `{} = \"{}\"`\n \
candidate versions found which didn't match: {}\n \
location searched: {}\n",
dep.package_name(),
dep.version_req(),
versions,
registry.describe_source(dep.source_id()),
);
msg.push_str("required by ");
msg.push_str(&describe_path(
&cx.parents.path_to_bottom(&parent.package_id()),
));

// If we have a path dependency with a locked version, then this may
// indicate that we updated a sub-package and forgot to run `cargo
// update`. In this case try to print a helpful error!
if dep.source_id().is_path() && dep.version_req().to_string().starts_with('=') {
msg.push_str(
"\nconsider running `cargo update` to update \
a path dependency's locked version",
);
}

if registry.is_replaced(dep.source_id()) {
msg.push_str("\nperhaps a crate was updated and forgotten to be re-vendored?");
}
if registry.is_replaced(dep.source_id()) {
msg.push_str("\nperhaps a crate was updated and forgotten to be re-vendored?");
}

msg
} else {
// Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
// was meant. So we try asking the registry for a `fuzzy` search for suggestions.
let mut candidates = Vec::new();
if let Err(e) = registry.query(&new_dep, &mut |s| candidates.push(s.name()), true) {
return to_resolve_err(e);
};
candidates.sort_unstable();
candidates.dedup();
let mut candidates: Vec<_> = candidates
.iter()
.map(|n| (lev_distance(&*new_dep.package_name(), &*n), n))
.filter(|&(d, _)| d < 4)
.collect();
candidates.sort_by_key(|o| o.0);
let mut msg = format!(
"no matching package named `{}` found\n\
location searched: {}\n",
dep.package_name(),
dep.source_id()
);
if !candidates.is_empty() {
let mut names = candidates
msg
} else {
// Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
// was meant. So we try asking the registry for a `fuzzy` search for suggestions.
let mut candidates = Vec::new();
if let Err(e) = registry.query(&new_dep, &mut |s| candidates.push(s.clone()), true) {
return to_resolve_err(e);
};
candidates.sort_unstable_by(|a, b| a.name().cmp(&b.name()));
candidates.dedup_by(|a, b| a.name() == b.name());
let mut candidates: Vec<_> = candidates
.iter()
.take(3)
.map(|c| c.1.as_str())
.collect::<Vec<_>>();

if candidates.len() > 3 {
names.push("...");
.map(|n| (lev_distance(&*new_dep.package_name(), &*n.name()), n))
.filter(|&(d, _)| d < 4)
.collect();
candidates.sort_by_key(|o| o.0);
let mut msg = format!(
"no matching package named `{}` found\n\
location searched: {}\n",
dep.package_name(),
dep.source_id()
);
if !candidates.is_empty() {
// If dependency package name is equal to the name of the candidate here
// it may be a prerelease package which hasn't been speficied correctly
if dep.package_name() == candidates[0].1.name()
&& candidates[0].1.package_id().version().is_prerelease()
{
msg.push_str("prerelease package needs to be specified explicitly\n");
msg.push_str(&format!(
"{name} = {{ version = \"{version}\" }}",
name = candidates[0].1.name(),
version = candidates[0].1.package_id().version()
));
} else {
let mut names = candidates
.iter()
.take(3)
.map(|c| c.1.name().as_str())
.collect::<Vec<_>>();

if candidates.len() > 3 {
names.push("...");
}

msg.push_str("perhaps you meant: ");
msg.push_str(&names.iter().enumerate().fold(
String::default(),
|acc, (i, el)| match i {
0 => acc + el,
i if names.len() - 1 == i && candidates.len() <= 3 => acc + " or " + el,
_ => acc + ", " + el,
},
));
}

msg.push_str("\n");
}

msg.push_str("perhaps you meant: ");
msg.push_str(&names.iter().enumerate().fold(
String::default(),
|acc, (i, el)| match i {
0 => acc + el,
i if names.len() - 1 == i && candidates.len() <= 3 => acc + " or " + el,
_ => acc + ", " + el,
},
msg.push_str("required by ");
msg.push_str(&describe_path(
&cx.parents.path_to_bottom(&parent.package_id()),
));
msg.push_str("\n");
}
msg.push_str("required by ");
msg.push_str(&describe_path(
&cx.parents.path_to_bottom(&parent.package_id()),
));

msg
};
msg
};

if let Some(config) = config {
if config.offline() {
Expand Down
49 changes: 49 additions & 0 deletions tests/testsuite/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,55 @@ fn use_semver() {
p.cargo("build").run();
}

#[cargo_test]
fn use_semver_package_incorrectly() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["a", "b"]
"#,
)
.file(
"a/Cargo.toml",
r#"
[project]
name = "a"
version = "0.1.1-alpha.0"
authors = []
"#,
)
.file(
"b/Cargo.toml",
r#"
[project]
name = "b"
version = "0.1.0"
authors = []

[dependencies]
a = { version = "^0.1", path = "../a" }
"#,
)
.file("a/src/main.rs", "fn main() {}")
.file("b/src/main.rs", "fn main() {}")
.build();

p.cargo("build")
.with_status(101)
.with_stderr(
"\
error: no matching package named `a` found
location searched: [..]
prerelease package needs to be specified explicitly
a = { version = \"0.1.1-alpha.0\" }
required by package `b v0.1.0 ([..])`
",
)
.run();
}

#[cargo_test]
fn only_download_relevant() {
let p = project()
Expand Down