Skip to main content

cargo/core/resolver/
errors.rs

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