-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #6175 - alexheretic:compile_package_errors, r=alexcrichton
Add PackageError wrappers for activation errors Similarly to #6157 this wraps compile errors with `PackageId` info to allow lib users, in this case rls, to discern where something went wrong. In particular if a dependency has a dodgy version or doesn't exist the error chain will now contain a <s>PackageError that provides the package id</s> `ResolveError` that provides the package path from error-parent -> root. From this I figure out if the error better relates to a workspace member, and target that manifest for diagnostics.
- Loading branch information
Showing
3 changed files
with
325 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
|
||
use core::{Dependency, PackageId, Registry, Summary}; | ||
use failure::{Error, Fail}; | ||
use semver; | ||
use util::config::Config; | ||
use util::lev_distance::lev_distance; | ||
|
||
use super::context::Context; | ||
use super::types::{Candidate, 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 Fail for ResolveError { | ||
fn cause(&self) -> Option<&Fail> { | ||
self.cause.as_fail().cause() | ||
} | ||
} | ||
|
||
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(super) fn activation_error( | ||
cx: &Context, | ||
registry: &mut Registry, | ||
parent: &Summary, | ||
dep: &Dependency, | ||
conflicting_activations: &HashMap<PackageId, ConflictReason>, | ||
candidates: &[Candidate], | ||
config: Option<&Config>, | ||
) -> ResolveError { | ||
let graph = cx.graph(); | ||
let to_resolve_err = |err| { | ||
ResolveError::new( | ||
err, | ||
graph | ||
.path_to_top(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(&graph.path_to_top(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.summary.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(&graph.path_to_top(p))); | ||
} | ||
|
||
let (features_errors, 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. | ||
} | ||
|
||
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(&graph.path_to_top(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(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(&graph.path_to_top(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.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 | ||
.iter() | ||
.take(3) | ||
.map(|c| c.1.as_str()) | ||
.collect::<Vec<_>>(); | ||
|
||
if candidates.len() > 3 { | ||
names.push("..."); | ||
} | ||
|
||
msg.push_str("did you mean: "); | ||
msg.push_str(&names.join(", ")); | ||
msg.push_str("\n"); | ||
} | ||
msg.push_str("required by "); | ||
msg.push_str(&describe_path(&graph.path_to_top(parent.package_id()))); | ||
|
||
msg | ||
}; | ||
|
||
if let Some(config) = config { | ||
if config.cli_unstable().offline { | ||
msg.push_str( | ||
"\nAs a reminder, you're using offline mode (-Z offline) \ | ||
which can sometimes cause surprising resolution failures, \ | ||
if this error is too confusing you may with to retry \ | ||
without the offline flag.", | ||
); | ||
} | ||
} | ||
|
||
to_resolve_err(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 | ||
} |
Oops, something went wrong.