Skip to content

Commit 5f5b211

Browse files
committed
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.
2 parents 09ce4b5 + a853efd commit 5f5b211

File tree

3 files changed

+325
-213
lines changed

3 files changed

+325
-213
lines changed

src/cargo/core/resolver/errors.rs

+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
use std::collections::HashMap;
2+
use std::fmt;
3+
4+
use core::{Dependency, PackageId, Registry, Summary};
5+
use failure::{Error, Fail};
6+
use semver;
7+
use util::config::Config;
8+
use util::lev_distance::lev_distance;
9+
10+
use super::context::Context;
11+
use super::types::{Candidate, ConflictReason};
12+
13+
/// Error during resolution providing a path of `PackageId`s.
14+
pub struct ResolveError {
15+
cause: Error,
16+
package_path: Vec<PackageId>,
17+
}
18+
19+
impl ResolveError {
20+
pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self {
21+
Self {
22+
cause: cause.into(),
23+
package_path,
24+
}
25+
}
26+
27+
/// Returns a path of packages from the package whose requirements could not be resolved up to
28+
/// the root.
29+
pub fn package_path(&self) -> &[PackageId] {
30+
&self.package_path
31+
}
32+
}
33+
34+
impl Fail for ResolveError {
35+
fn cause(&self) -> Option<&Fail> {
36+
self.cause.as_fail().cause()
37+
}
38+
}
39+
40+
impl fmt::Debug for ResolveError {
41+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42+
self.cause.fmt(f)
43+
}
44+
}
45+
46+
impl fmt::Display for ResolveError {
47+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48+
self.cause.fmt(f)
49+
}
50+
}
51+
52+
pub(super) fn activation_error(
53+
cx: &Context,
54+
registry: &mut Registry,
55+
parent: &Summary,
56+
dep: &Dependency,
57+
conflicting_activations: &HashMap<PackageId, ConflictReason>,
58+
candidates: &[Candidate],
59+
config: Option<&Config>,
60+
) -> ResolveError {
61+
let graph = cx.graph();
62+
let to_resolve_err = |err| {
63+
ResolveError::new(
64+
err,
65+
graph
66+
.path_to_top(parent.package_id())
67+
.into_iter()
68+
.cloned()
69+
.collect(),
70+
)
71+
};
72+
73+
if !candidates.is_empty() {
74+
let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
75+
msg.push_str("\n ... required by ");
76+
msg.push_str(&describe_path(&graph.path_to_top(parent.package_id())));
77+
78+
msg.push_str("\nversions that meet the requirements `");
79+
msg.push_str(&dep.version_req().to_string());
80+
msg.push_str("` are: ");
81+
msg.push_str(
82+
&candidates
83+
.iter()
84+
.map(|v| v.summary.version())
85+
.map(|v| v.to_string())
86+
.collect::<Vec<_>>()
87+
.join(", "),
88+
);
89+
90+
let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
91+
conflicting_activations.sort_unstable();
92+
let (links_errors, mut other_errors): (Vec<_>, Vec<_>) = conflicting_activations
93+
.drain(..)
94+
.rev()
95+
.partition(|&(_, r)| r.is_links());
96+
97+
for &(p, r) in links_errors.iter() {
98+
if let ConflictReason::Links(ref link) = *r {
99+
msg.push_str("\n\nthe package `");
100+
msg.push_str(&*dep.package_name());
101+
msg.push_str("` links to the native library `");
102+
msg.push_str(link);
103+
msg.push_str("`, but it conflicts with a previous package which links to `");
104+
msg.push_str(link);
105+
msg.push_str("` as well:\n");
106+
}
107+
msg.push_str(&describe_path(&graph.path_to_top(p)));
108+
}
109+
110+
let (features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors
111+
.drain(..)
112+
.partition(|&(_, r)| r.is_missing_features());
113+
114+
for &(p, r) in features_errors.iter() {
115+
if let ConflictReason::MissingFeatures(ref features) = *r {
116+
msg.push_str("\n\nthe package `");
117+
msg.push_str(&*p.name());
118+
msg.push_str("` depends on `");
119+
msg.push_str(&*dep.package_name());
120+
msg.push_str("`, with features: `");
121+
msg.push_str(features);
122+
msg.push_str("` but `");
123+
msg.push_str(&*dep.package_name());
124+
msg.push_str("` does not have these features.\n");
125+
}
126+
// p == parent so the full path is redundant.
127+
}
128+
129+
if !other_errors.is_empty() {
130+
msg.push_str(
131+
"\n\nall possible versions conflict with \
132+
previously selected packages.",
133+
);
134+
}
135+
136+
for &(p, _) in other_errors.iter() {
137+
msg.push_str("\n\n previously selected ");
138+
msg.push_str(&describe_path(&graph.path_to_top(p)));
139+
}
140+
141+
msg.push_str("\n\nfailed to select a version for `");
142+
msg.push_str(&*dep.package_name());
143+
msg.push_str("` which could resolve this conflict");
144+
145+
return to_resolve_err(format_err!("{}", msg));
146+
}
147+
148+
// We didn't actually find any candidates, so we need to
149+
// give an error message that nothing was found.
150+
//
151+
// Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
152+
// was meant. So we re-query the registry with `deb="*"` so we can
153+
// list a few versions that were actually found.
154+
let all_req = semver::VersionReq::parse("*").unwrap();
155+
let mut new_dep = dep.clone();
156+
new_dep.set_version_req(all_req);
157+
let mut candidates = match registry.query_vec(&new_dep, false) {
158+
Ok(candidates) => candidates,
159+
Err(e) => return to_resolve_err(e),
160+
};
161+
candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));
162+
163+
let mut msg = if !candidates.is_empty() {
164+
let versions = {
165+
let mut versions = candidates
166+
.iter()
167+
.take(3)
168+
.map(|cand| cand.version().to_string())
169+
.collect::<Vec<_>>();
170+
171+
if candidates.len() > 3 {
172+
versions.push("...".into());
173+
}
174+
175+
versions.join(", ")
176+
};
177+
178+
let mut msg = format!(
179+
"failed to select a version for the requirement `{} = \"{}\"`\n \
180+
candidate versions found which didn't match: {}\n \
181+
location searched: {}\n",
182+
dep.package_name(),
183+
dep.version_req(),
184+
versions,
185+
registry.describe_source(dep.source_id()),
186+
);
187+
msg.push_str("required by ");
188+
msg.push_str(&describe_path(&graph.path_to_top(parent.package_id())));
189+
190+
// If we have a path dependency with a locked version, then this may
191+
// indicate that we updated a sub-package and forgot to run `cargo
192+
// update`. In this case try to print a helpful error!
193+
if dep.source_id().is_path() && dep.version_req().to_string().starts_with('=') {
194+
msg.push_str(
195+
"\nconsider running `cargo update` to update \
196+
a path dependency's locked version",
197+
);
198+
}
199+
200+
if registry.is_replaced(dep.source_id()) {
201+
msg.push_str("\nperhaps a crate was updated and forgotten to be re-vendored?");
202+
}
203+
204+
msg
205+
} else {
206+
// Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
207+
// was meant. So we try asking the registry for a `fuzzy` search for suggestions.
208+
let mut candidates = Vec::new();
209+
if let Err(e) = registry.query(&new_dep, &mut |s| candidates.push(s.name()), true) {
210+
return to_resolve_err(e);
211+
};
212+
candidates.sort_unstable();
213+
candidates.dedup();
214+
let mut candidates: Vec<_> = candidates
215+
.iter()
216+
.map(|n| (lev_distance(&*new_dep.package_name(), &*n), n))
217+
.filter(|&(d, _)| d < 4)
218+
.collect();
219+
candidates.sort_by_key(|o| o.0);
220+
let mut msg = format!(
221+
"no matching package named `{}` found\n\
222+
location searched: {}\n",
223+
dep.package_name(),
224+
dep.source_id()
225+
);
226+
if !candidates.is_empty() {
227+
let mut names = candidates
228+
.iter()
229+
.take(3)
230+
.map(|c| c.1.as_str())
231+
.collect::<Vec<_>>();
232+
233+
if candidates.len() > 3 {
234+
names.push("...");
235+
}
236+
237+
msg.push_str("did you mean: ");
238+
msg.push_str(&names.join(", "));
239+
msg.push_str("\n");
240+
}
241+
msg.push_str("required by ");
242+
msg.push_str(&describe_path(&graph.path_to_top(parent.package_id())));
243+
244+
msg
245+
};
246+
247+
if let Some(config) = config {
248+
if config.cli_unstable().offline {
249+
msg.push_str(
250+
"\nAs a reminder, you're using offline mode (-Z offline) \
251+
which can sometimes cause surprising resolution failures, \
252+
if this error is too confusing you may with to retry \
253+
without the offline flag.",
254+
);
255+
}
256+
}
257+
258+
to_resolve_err(format_err!("{}", msg))
259+
}
260+
261+
/// Returns String representation of dependency chain for a particular `pkgid`.
262+
pub(super) fn describe_path(path: &[&PackageId]) -> String {
263+
use std::fmt::Write;
264+
let mut dep_path_desc = format!("package `{}`", path[0]);
265+
for dep in path[1..].iter() {
266+
write!(dep_path_desc, "\n ... which is depended on by `{}`", dep).unwrap();
267+
}
268+
dep_path_desc
269+
}

0 commit comments

Comments
 (0)