Skip to main content

cargo/core/resolver/
errors.rs

1use std::fmt;
2use std::fmt::Write as _;
3
4use crate::core::{Dependency, PackageId, Registry, Summary};
5use crate::sources::IndexSummary;
6use crate::sources::source::QueryKind;
7use crate::util::edit_distance::{closest, edit_distance};
8use crate::util::errors::CargoResult;
9use crate::util::{GlobalContext, OptVersionReq, VersionExt};
10use anyhow::Error;
11
12use super::context::ResolverContext;
13use super::types::{ConflictMap, ConflictReason};
14
15/// Error during resolution providing a path of `PackageId`s.
16pub struct ResolveError {
17    cause: Error,
18    package_path: Vec<PackageId>,
19}
20
21impl ResolveError {
22    pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self {
23        Self {
24            cause: cause.into(),
25            package_path,
26        }
27    }
28
29    /// Returns a path of packages from the package whose requirements could not be resolved up to
30    /// the root.
31    pub fn package_path(&self) -> &[PackageId] {
32        &self.package_path
33    }
34}
35
36impl std::error::Error for ResolveError {
37    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38        self.cause.source()
39    }
40}
41
42impl fmt::Debug for ResolveError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        self.cause.fmt(f)
45    }
46}
47
48impl fmt::Display for ResolveError {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        self.cause.fmt(f)
51    }
52}
53
54pub type ActivateResult<T> = Result<T, ActivateError>;
55
56#[derive(Debug)]
57pub enum ActivateError {
58    Fatal(anyhow::Error),
59    Conflict(PackageId, ConflictReason),
60}
61
62impl From<::anyhow::Error> for ActivateError {
63    fn from(t: ::anyhow::Error) -> Self {
64        ActivateError::Fatal(t)
65    }
66}
67
68impl From<(PackageId, ConflictReason)> for ActivateError {
69    fn from(t: (PackageId, ConflictReason)) -> Self {
70        ActivateError::Conflict(t.0, t.1)
71    }
72}
73
74pub(super) fn activation_error(
75    resolver_ctx: &ResolverContext,
76    registry: &impl Registry,
77    parent: &Summary,
78    dep: &Dependency,
79    conflicting_activations: &ConflictMap,
80    candidates: &[Summary],
81    gctx: Option<&GlobalContext>,
82) -> ResolveError {
83    let to_resolve_err = |err| {
84        ResolveError::new(
85            err,
86            resolver_ctx
87                .parents
88                .path_to_bottom(&parent.package_id())
89                .into_iter()
90                .map(|(node, _)| node)
91                .cloned()
92                .collect(),
93        )
94    };
95
96    if !candidates.is_empty() {
97        let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
98        msg.push_str("\n    ... required by ");
99        msg.push_str(&describe_path_in_context(
100            resolver_ctx,
101            &parent.package_id(),
102        ));
103
104        msg.push_str("\nversions that meet the requirements `");
105        msg.push_str(&dep.version_req().to_string());
106        msg.push_str("` ");
107
108        if let Some(v) = dep.version_req().locked_version() {
109            msg.push_str("(locked to ");
110            msg.push_str(&v.to_string());
111            msg.push_str(") ");
112        }
113
114        msg.push_str("are: ");
115        msg.push_str(
116            &candidates
117                .iter()
118                .map(|v| v.version())
119                .map(|v| v.to_string())
120                .collect::<Vec<_>>()
121                .join(", "),
122        );
123
124        let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
125        conflicting_activations.sort_unstable();
126        // This is reversed to show the newest versions first. I don't know if there is
127        // a strong reason to do this, but that is how the code previously worked
128        // (see https://github.com/rust-lang/cargo/pull/5037) and I don't feel like changing it.
129        conflicting_activations.reverse();
130        // Flag used for grouping all semver errors together.
131        let mut has_semver = false;
132
133        for (p, r) in &conflicting_activations {
134            match r {
135                ConflictReason::Semver => {
136                    has_semver = true;
137                }
138                ConflictReason::Links(link) => {
139                    msg.push_str("\n\npackage `");
140                    msg.push_str(&*dep.package_name());
141                    msg.push_str("` links to the native library `");
142                    msg.push_str(link);
143                    msg.push_str("`, but it conflicts with a previous package which links to `");
144                    msg.push_str(link);
145                    msg.push_str("` as well:\n");
146                    msg.push_str(&describe_path_in_context(resolver_ctx, p));
147                    msg.push_str("\nnote: only one package in the dependency graph may specify the same links value to ensure that only one copy of a native library is linked in the final binary");
148                    msg.push_str("\nfor more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links");
149                    msg.push_str("\nhelp: try to adjust your dependencies so that only one package uses the `links = \"");
150                    msg.push_str(link);
151                    msg.push_str("\"` value");
152                }
153                ConflictReason::MissingFeature(feature) => {
154                    msg.push_str("\n\npackage `");
155                    msg.push_str(&*p.name());
156                    msg.push_str("` depends on `");
157                    msg.push_str(&*dep.package_name());
158                    msg.push_str("` with feature `");
159                    msg.push_str(feature);
160                    msg.push_str("` but `");
161                    msg.push_str(&*dep.package_name());
162                    msg.push_str("` does not have that feature.\n");
163                    let latest = candidates.last().expect("in the non-empty branch");
164                    if let Some(closest) = closest(feature, latest.features().keys(), |k| k) {
165                        msg.push_str("help: there is a feature `");
166                        msg.push_str(closest);
167                        msg.push_str("` with a similar name\n");
168                    } else if !latest.features().is_empty() {
169                        let mut features: Vec<_> =
170                            latest.features().keys().map(|f| f.as_str()).collect();
171                        features.sort();
172                        msg.push_str("help: available features: ");
173                        msg.push_str(&features.join(", "));
174                        msg.push_str("\n");
175                    }
176                    // p == parent so the full path is redundant.
177                }
178                ConflictReason::RequiredDependencyAsFeature(feature) => {
179                    msg.push_str("\n\npackage `");
180                    msg.push_str(&*p.name());
181                    msg.push_str("` depends on `");
182                    msg.push_str(&*dep.package_name());
183                    msg.push_str("` with feature `");
184                    msg.push_str(feature);
185                    msg.push_str("` but `");
186                    msg.push_str(&*dep.package_name());
187                    msg.push_str("` does not have that feature.\n");
188                    msg.push_str(
189                        "note: a required dependency with that name exists, \
190                         but only optional dependencies can be used as features.\n",
191                    );
192                    // p == parent so the full path is redundant.
193                }
194                ConflictReason::NonImplicitDependencyAsFeature(feature) => {
195                    msg.push_str("\n\npackage `");
196                    msg.push_str(&*p.name());
197                    msg.push_str("` depends on `");
198                    msg.push_str(&*dep.package_name());
199                    msg.push_str("` with feature `");
200                    msg.push_str(feature);
201                    msg.push_str("` but `");
202                    msg.push_str(&*dep.package_name());
203                    msg.push_str("` does not have that feature.\n");
204                    msg.push_str(
205                        "note: an optional dependency with that name exists, \
206                         but that dependency uses the \"dep:\" \
207                         syntax in the features table, so it does not have an \
208                         implicit feature with that name.\n",
209                    );
210                    // p == parent so the full path is redundant.
211                }
212            }
213        }
214
215        if has_semver {
216            // Group these errors together.
217            msg.push_str("\n\nall possible versions conflict with previously selected packages");
218            for (p, r) in &conflicting_activations {
219                if let ConflictReason::Semver = r {
220                    msg.push_str("\n\n  previously selected ");
221                    msg.push_str(&describe_path_in_context(resolver_ctx, p));
222                }
223            }
224        }
225
226        msg.push_str("\n\nfailed to select a version for `");
227        msg.push_str(&*dep.package_name());
228        msg.push_str("` which could resolve this conflict");
229
230        return to_resolve_err(anyhow::format_err!("{}", msg));
231    }
232
233    // We didn't actually find any candidates, so we need to
234    // give an error message that nothing was found.
235    let mut msg = String::new();
236    let mut hints = String::new();
237    if let Some(version_candidates) = rejected_versions(registry, dep) {
238        let version_candidates = match version_candidates {
239            Ok(c) => c,
240            Err(e) => return to_resolve_err(e),
241        };
242
243        let locked_version = dep
244            .version_req()
245            .locked_version()
246            .map(|v| format!(" (locked to {})", v))
247            .unwrap_or_default();
248        let _ = writeln!(
249            &mut msg,
250            "failed to select a version for the requirement `{} = \"{}\"`{}",
251            dep.package_name(),
252            dep.version_req(),
253            locked_version
254        );
255        for candidate in version_candidates {
256            match candidate {
257                IndexSummary::Candidate(summary) => {
258                    // HACK: If this was a real candidate, we wouldn't hit this case.
259                    // so it must be a patch which get normalized to being a candidate
260                    let _ = writeln!(&mut msg, "  version {} is unavailable", summary.version());
261                }
262                IndexSummary::Yanked(summary) => {
263                    let _ = writeln!(&mut msg, "  version {} is yanked", summary.version());
264                }
265                IndexSummary::Offline(summary) => {
266                    let _ = writeln!(&mut msg, "  version {} is not cached", summary.version());
267                }
268                IndexSummary::Unsupported(summary, schema_version) => {
269                    if let Some(rust_version) = summary.rust_version() {
270                        // HACK: technically its unsupported and we shouldn't make assumptions
271                        // about the entry but this is limited and for diagnostics purposes
272                        let _ = writeln!(
273                            &mut msg,
274                            "  version {} requires cargo {}",
275                            summary.version(),
276                            rust_version
277                        );
278                    } else {
279                        let _ = writeln!(
280                            &mut msg,
281                            "  version {} requires a Cargo version that supports index version {}",
282                            summary.version(),
283                            schema_version
284                        );
285                    }
286                }
287                IndexSummary::Invalid(summary) => {
288                    let _ = writeln!(
289                        &mut msg,
290                        "  version {}'s index entry is invalid",
291                        summary.version()
292                    );
293                }
294            }
295        }
296    } else if let Some(candidates) = alt_versions(registry, dep) {
297        let candidates = match candidates {
298            Ok(c) => c,
299            Err(e) => return to_resolve_err(e),
300        };
301        let versions = {
302            let mut versions = candidates
303                .iter()
304                .take(3)
305                .map(|cand| cand.version().to_string())
306                .collect::<Vec<_>>();
307
308            if candidates.len() > 3 {
309                versions.push("...".into());
310            }
311
312            versions.join(", ")
313        };
314
315        let locked_version = dep
316            .version_req()
317            .locked_version()
318            .map(|v| format!(" (locked to {})", v))
319            .unwrap_or_default();
320
321        let _ = writeln!(
322            &mut msg,
323            "failed to select a version for the requirement `{} = \"{}\"`{}",
324            dep.package_name(),
325            dep.version_req(),
326            locked_version,
327        );
328        let _ = writeln!(
329            &mut msg,
330            "candidate versions found which didn't match: {versions}",
331        );
332
333        // If we have a pre-release candidate, then that may be what our user is looking for
334        if let Some(pre) = candidates.iter().find(|c| c.version().is_prerelease()) {
335            let _ = write!(
336                &mut hints,
337                "\nhelp: if you are looking for the prerelease package it needs to be specified explicitly"
338            );
339            let _ = write!(
340                &mut hints,
341                "\n    {} = {{ version = \"{}\" }}",
342                pre.name(),
343                pre.version()
344            );
345        }
346
347        // If we have a path dependency with a locked version, then this may
348        // indicate that we updated a sub-package and forgot to run `cargo
349        // update`. In this case try to print a helpful error!
350        if dep.source_id().is_path() && dep.version_req().is_locked() {
351            let _ = write!(
352                &mut hints,
353                "\nhelp: to update a path dependency's locked version, run `cargo update`",
354            );
355        }
356
357        if registry.is_replaced(dep.source_id()) {
358            let _ = write!(
359                &mut hints,
360                "\nnote: perhaps a crate was updated and forgotten to be re-vendored?"
361            );
362        }
363    } else if let Some(name_candidates) = alt_names(registry, dep) {
364        let name_candidates = match name_candidates {
365            Ok(c) => c,
366            Err(e) => return to_resolve_err(e),
367        };
368        let _ = writeln!(
369            &mut msg,
370            "no matching package named `{}` found",
371            dep.package_name()
372        );
373
374        let mut names = name_candidates
375            .iter()
376            .take(3)
377            .map(|c| c.1.name().as_str())
378            .collect::<Vec<_>>();
379        if name_candidates.len() > 3 {
380            names.push("...");
381        }
382        let suggestions =
383            names
384                .iter()
385                .enumerate()
386                .fold(String::default(), |acc, (i, el)| match i {
387                    0 => acc + el,
388                    i if names.len() - 1 == i && name_candidates.len() <= 3 => acc + " or " + el,
389                    _ => acc + ", " + el,
390                });
391        let _ = writeln!(
392            &mut hints,
393            "\nhelp: packages with similar names: {suggestions}"
394        );
395    } else {
396        let _ = writeln!(
397            &mut msg,
398            "no matching package named `{}` found",
399            dep.package_name()
400        );
401    }
402
403    let mut location_searched_msg = registry.describe_source(dep.source_id());
404    if location_searched_msg.is_empty() {
405        location_searched_msg = format!("{}", dep.source_id());
406    }
407    let _ = writeln!(&mut msg, "location searched: {}", location_searched_msg);
408    let _ = write!(
409        &mut msg,
410        "required by {}",
411        describe_path_in_context(resolver_ctx, &parent.package_id()),
412    );
413
414    if let Some(gctx) = gctx {
415        if let Some(offline_flag) = gctx.offline_flag() {
416            let _ = write!(
417                &mut hints,
418                "\nnote: offline mode (via `{offline_flag}`) \
419                 can sometimes cause surprising resolution failures\
420                 \nhelp: if this error is too confusing you may wish to retry \
421                 without `{offline_flag}`",
422            );
423        }
424    }
425
426    to_resolve_err(anyhow::format_err!("{msg}{hints}"))
427}
428
429// Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
430// was meant. So we re-query the registry with `dep="*"` so we can
431// list a few versions that were actually found.
432fn alt_versions(registry: &impl Registry, dep: &Dependency) -> Option<CargoResult<Vec<Summary>>> {
433    let mut wild_dep = dep.clone();
434    wild_dep.set_version_req(OptVersionReq::Any);
435
436    let candidates = match crate::util::block_on(registry.query_vec(&wild_dep, QueryKind::Exact)) {
437        Ok(candidates) => candidates,
438        Err(e) => return Some(Err(e)),
439    };
440    let mut candidates: Vec<_> = candidates.into_iter().map(|s| s.into_summary()).collect();
441    candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));
442    if candidates.is_empty() {
443        None
444    } else {
445        Some(Ok(candidates))
446    }
447}
448
449/// Maybe something is wrong with the available versions
450fn rejected_versions(
451    registry: &impl Registry,
452    dep: &Dependency,
453) -> Option<CargoResult<Vec<IndexSummary>>> {
454    let mut version_candidates =
455        match crate::util::block_on(registry.query_vec(&dep, QueryKind::RejectedVersions)) {
456            Ok(candidates) => candidates,
457            Err(e) => return Some(Err(e)),
458        };
459    version_candidates.sort_unstable_by_key(|a| a.as_summary().version().clone());
460    if version_candidates.is_empty() {
461        None
462    } else {
463        Some(Ok(version_candidates))
464    }
465}
466
467/// Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
468/// was meant. So we try asking the registry for a `fuzzy` search for suggestions.
469fn alt_names(
470    registry: &impl Registry,
471    dep: &Dependency,
472) -> Option<CargoResult<Vec<(usize, Summary)>>> {
473    let mut wild_dep = dep.clone();
474    wild_dep.set_version_req(OptVersionReq::Any);
475
476    let name_candidates =
477        match crate::util::block_on(registry.query_vec(&wild_dep, QueryKind::AlternativeNames)) {
478            Ok(candidates) => candidates,
479            Err(e) => return Some(Err(e)),
480        };
481    let mut name_candidates: Vec<_> = name_candidates
482        .into_iter()
483        .map(|s| s.into_summary())
484        .collect();
485    name_candidates.sort_unstable_by_key(|a| a.name());
486    name_candidates.dedup_by(|a, b| a.name() == b.name());
487    let mut name_candidates: Vec<_> = name_candidates
488        .into_iter()
489        .filter_map(|n| Some((edit_distance(&*wild_dep.package_name(), &*n.name(), 3)?, n)))
490        .collect();
491    name_candidates.sort_by_key(|o| o.0);
492
493    if name_candidates.is_empty() {
494        None
495    } else {
496        Some(Ok(name_candidates))
497    }
498}
499
500/// Returns String representation of dependency chain for a particular `pkgid`
501/// within given context.
502pub(super) fn describe_path_in_context(cx: &ResolverContext, id: &PackageId) -> String {
503    let iter = cx
504        .parents
505        .path_to_bottom(id)
506        .into_iter()
507        .map(|(p, d)| (p, d.and_then(|d| d.iter().next())));
508    describe_path(iter)
509}
510
511/// Returns String representation of dependency chain for a particular `pkgid`.
512///
513/// Note that all elements of `path` iterator should have `Some` dependency
514/// except the first one. It would look like:
515///
516/// (pkg0, None)
517/// -> (pkg1, dep from pkg1 satisfied by pkg0)
518/// -> (pkg2, dep from pkg2 satisfied by pkg1)
519/// -> ...
520pub(crate) fn describe_path<'a>(
521    mut path: impl Iterator<Item = (&'a PackageId, Option<&'a Dependency>)>,
522) -> String {
523    use std::fmt::Write;
524
525    if let Some(p) = path.next() {
526        let mut dep_path_desc = format!("package `{}`", p.0);
527        for (pkg, dep) in path {
528            let dep = dep.unwrap();
529            let source_kind = if dep.source_id().is_path() {
530                "path "
531            } else if dep.source_id().is_git() {
532                "git "
533            } else {
534                ""
535            };
536            let requirement = if source_kind.is_empty() {
537                format!("{} = \"{}\"", dep.name_in_toml(), dep.version_req())
538            } else {
539                dep.name_in_toml().to_string()
540            };
541            let locked_version = dep
542                .version_req()
543                .locked_version()
544                .map(|v| format!("(locked to {}) ", v))
545                .unwrap_or_default();
546
547            write!(
548                dep_path_desc,
549                "\n    ... which satisfies {}dependency `{}` {}of package `{}`",
550                source_kind, requirement, locked_version, pkg
551            )
552            .unwrap();
553        }
554
555        return dep_path_desc;
556    }
557
558    String::new()
559}