blob: 68ed530e539e1c989dea5daf8f1df49ae1ddc117 [file] [log] [blame]
use std::fmt;
use crate::core::{Dependency, PackageId, Registry, Summary};
use crate::util::lev_distance::lev_distance;
use crate::util::Config;
use anyhow::Error;
use super::context::Context;
use super::types::{ConflictMap, ConflictReason};
/// Error during resolution providing a path of `PackageId`s.
pub struct ResolveError {
cause: Error,
package_path: Vec<PackageId>,
}
impl ResolveError {
pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self {
Self {
cause: cause.into(),
package_path,
}
}
/// Returns a path of packages from the package whose requirements could not be resolved up to
/// the root.
pub fn package_path(&self) -> &[PackageId] {
&self.package_path
}
}
impl std::error::Error for ResolveError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.cause.source()
}
}
impl fmt::Debug for ResolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.cause.fmt(f)
}
}
impl fmt::Display for ResolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.cause.fmt(f)
}
}
pub type ActivateResult<T> = Result<T, ActivateError>;
#[derive(Debug)]
pub enum ActivateError {
Fatal(anyhow::Error),
Conflict(PackageId, ConflictReason),
}
impl From<::anyhow::Error> for ActivateError {
fn from(t: ::anyhow::Error) -> Self {
ActivateError::Fatal(t)
}
}
impl From<(PackageId, ConflictReason)> for ActivateError {
fn from(t: (PackageId, ConflictReason)) -> Self {
ActivateError::Conflict(t.0, t.1)
}
}
pub(super) fn activation_error(
cx: &Context,
registry: &mut dyn Registry,
parent: &Summary,
dep: &Dependency,
conflicting_activations: &ConflictMap,
candidates: &[Summary],
config: Option<&Config>,
) -> ResolveError {
let to_resolve_err = |err| {
ResolveError::new(
err,
cx.parents
.path_to_bottom(&parent.package_id())
.into_iter()
.cloned()
.collect(),
)
};
if !candidates.is_empty() {
let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
msg.push_str("\n ... required by ");
msg.push_str(&describe_path(
&cx.parents.path_to_bottom(&parent.package_id()),
));
msg.push_str("\nversions that meet the requirements `");
msg.push_str(&dep.version_req().to_string());
msg.push_str("` are: ");
msg.push_str(
&candidates
.iter()
.map(|v| v.version())
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", "),
);
let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
conflicting_activations.sort_unstable();
let (links_errors, mut other_errors): (Vec<_>, Vec<_>) = conflicting_activations
.drain(..)
.rev()
.partition(|&(_, r)| r.is_links());
for &(p, r) in links_errors.iter() {
if let ConflictReason::Links(ref link) = *r {
msg.push_str("\n\nthe package `");
msg.push_str(&*dep.package_name());
msg.push_str("` links to the native library `");
msg.push_str(link);
msg.push_str("`, but it conflicts with a previous package which links to `");
msg.push_str(link);
msg.push_str("` as well:\n");
}
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
}
let (features_errors, mut other_errors): (Vec<_>, Vec<_>) = other_errors
.drain(..)
.partition(|&(_, r)| r.is_missing_features());
for &(p, r) in features_errors.iter() {
if let ConflictReason::MissingFeatures(ref features) = *r {
msg.push_str("\n\nthe package `");
msg.push_str(&*p.name());
msg.push_str("` depends on `");
msg.push_str(&*dep.package_name());
msg.push_str("`, with features: `");
msg.push_str(features);
msg.push_str("` but `");
msg.push_str(&*dep.package_name());
msg.push_str("` does not have these features.\n");
}
// p == parent so the full path is redundant.
}
let (required_dependency_as_features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors
.drain(..)
.partition(|&(_, r)| r.is_required_dependency_as_features());
for &(p, r) in required_dependency_as_features_errors.iter() {
if let ConflictReason::RequiredDependencyAsFeatures(ref features) = *r {
msg.push_str("\n\nthe package `");
msg.push_str(&*p.name());
msg.push_str("` depends on `");
msg.push_str(&*dep.package_name());
msg.push_str("`, with features: `");
msg.push_str(features);
msg.push_str("` but `");
msg.push_str(&*dep.package_name());
msg.push_str("` does not have these features.\n");
msg.push_str(
" It has a required dependency with that name, \
but only optional dependencies can be used as features.\n",
);
}
// p == parent so the full path is redundant.
}
if !other_errors.is_empty() {
msg.push_str(
"\n\nall possible versions conflict with \
previously selected packages.",
);
}
for &(p, _) in other_errors.iter() {
msg.push_str("\n\n previously selected ");
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
}
msg.push_str("\n\nfailed to select a version for `");
msg.push_str(&*dep.package_name());
msg.push_str("` which could resolve this conflict");
return to_resolve_err(anyhow::format_err!("{}", msg));
}
// We didn't actually find any candidates, so we need to
// give an error message that nothing was found.
//
// Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
// was meant. So we re-query the registry with `deb="*"` so we can
// list a few versions that were actually found.
let all_req = semver::VersionReq::parse("*").unwrap();
let mut new_dep = dep.clone();
new_dep.set_version_req(all_req);
let mut candidates = match registry.query_vec(&new_dep, false) {
Ok(candidates) => candidates,
Err(e) => return to_resolve_err(e),
};
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<_>>();
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 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?");
}
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), 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()
.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 specified 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("required by ");
msg.push_str(&describe_path(
&cx.parents.path_to_bottom(&parent.package_id()),
));
msg
};
if let Some(config) = config {
if config.offline() {
msg.push_str(
"\nAs a reminder, you're using offline mode (--offline) \
which can sometimes cause surprising resolution failures, \
if this error is too confusing you may wish to retry \
without the offline flag.",
);
}
}
to_resolve_err(anyhow::format_err!("{}", msg))
}
/// Returns String representation of dependency chain for a particular `pkgid`.
pub(super) fn describe_path(path: &[&PackageId]) -> String {
use std::fmt::Write;
let mut dep_path_desc = format!("package `{}`", path[0]);
for dep in path[1..].iter() {
write!(dep_path_desc, "\n ... which is depended on by `{}`", dep).unwrap();
}
dep_path_desc
}