Skip to main content

cargo/resolver/
errors.rs

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