cargo/core/resolver/
errors.rs

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