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::VersionPreferences;
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: &impl Registry,
78    version_prefs: &VersionPreferences,
79    parent: &Summary,
80    dep: &Dependency,
81    conflicting_activations: &ConflictMap,
82    candidates: &[Summary],
83    gctx: Option<&GlobalContext>,
84) -> ResolveError {
85    let to_resolve_err = |err| {
86        ResolveError::new(
87            err,
88            resolver_ctx
89                .parents
90                .path_to_bottom(&parent.package_id())
91                .into_iter()
92                .map(|(node, _)| node)
93                .cloned()
94                .collect(),
95        )
96    };
97
98    if !candidates.is_empty() {
99        let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
100        msg.push_str("\n    ... required by ");
101        msg.push_str(&describe_path_in_context(
102            resolver_ctx,
103            &parent.package_id(),
104        ));
105
106        msg.push_str("\nversions that meet the requirements `");
107        msg.push_str(&dep.version_req().to_string());
108        msg.push_str("` ");
109
110        if let Some(v) = dep.version_req().locked_version() {
111            msg.push_str("(locked to ");
112            msg.push_str(&v.to_string());
113            msg.push_str(") ");
114        }
115
116        msg.push_str("are: ");
117        msg.push_str(
118            &candidates
119                .iter()
120                .map(|v| v.version())
121                .map(|v| v.to_string())
122                .collect::<Vec<_>>()
123                .join(", "),
124        );
125
126        let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
127        conflicting_activations.sort_unstable();
128        // This is reversed to show the newest versions first. I don't know if there is
129        // a strong reason to do this, but that is how the code previously worked
130        // (see https://github.com/rust-lang/cargo/pull/5037) and I don't feel like changing it.
131        conflicting_activations.reverse();
132        // Flag used for grouping all semver errors together.
133        let mut has_semver = false;
134
135        for (p, r) in &conflicting_activations {
136            match r {
137                ConflictReason::Semver => {
138                    has_semver = true;
139                }
140                ConflictReason::Links(link) => {
141                    msg.push_str("\n\npackage `");
142                    msg.push_str(&*dep.package_name());
143                    msg.push_str("` links to the native library `");
144                    msg.push_str(link);
145                    msg.push_str("`, but it conflicts with a previous package which links to `");
146                    msg.push_str(link);
147                    msg.push_str("` as well:\n");
148                    msg.push_str(&describe_path_in_context(resolver_ctx, p));
149                    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");
150                    msg.push_str("\nfor more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links");
151                    msg.push_str("\nhelp: try to adjust your dependencies so that only one package uses the `links = \"");
152                    msg.push_str(link);
153                    msg.push_str("\"` value");
154                }
155                ConflictReason::MissingFeature(feature) => {
156                    msg.push_str("\n\npackage `");
157                    msg.push_str(&*p.name());
158                    msg.push_str("` depends on `");
159                    msg.push_str(&*dep.package_name());
160                    msg.push_str("` with feature `");
161                    msg.push_str(feature);
162                    msg.push_str("` but `");
163                    msg.push_str(&*dep.package_name());
164                    msg.push_str("` does not have that feature.\n");
165                    let latest = candidates.last().expect("in the non-empty branch");
166                    if let Some(closest) = closest(feature, latest.features().keys(), |k| k) {
167                        msg.push_str("help: there is a feature `");
168                        msg.push_str(closest);
169                        msg.push_str("` with a similar name\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("help: 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                        "note: 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                        "note: 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    // Whether any candidate was rejected for being newer than `min-publish-age`,
240    let mut has_too_new = false;
241    if let Some(version_candidates) = rejected_versions(registry, dep) {
242        let version_candidates = match version_candidates {
243            Ok(c) => c,
244            Err(e) => return to_resolve_err(e),
245        };
246
247        let locked_version = dep
248            .version_req()
249            .locked_version()
250            .map(|v| format!(" (locked to {})", v))
251            .unwrap_or_default();
252        let _ = writeln!(
253            &mut msg,
254            "failed to select a version for the requirement `{} = \"{}\"`{}",
255            dep.package_name(),
256            dep.version_req(),
257            locked_version
258        );
259        for candidate in version_candidates {
260            match candidate {
261                IndexSummary::Candidate(summary) => {
262                    if let Some(violation) = version_prefs.too_new(&summary) {
263                        has_too_new = true;
264                        let note = violation.note();
265                        let _ = writeln!(
266                            &mut msg,
267                            "  version {} is too new ({note})",
268                            summary.version(),
269                        );
270                    } else {
271                        // HACK: If this was a real candidate, we wouldn't hit this case.
272                        // so it must be a patch which get normalized to being a candidate
273                        let _ =
274                            writeln!(&mut msg, "  version {} is unavailable", summary.version());
275                    }
276                }
277                IndexSummary::Yanked(summary) => {
278                    let _ = writeln!(&mut msg, "  version {} is yanked", summary.version());
279                }
280                IndexSummary::Offline(summary) => {
281                    let _ = writeln!(&mut msg, "  version {} is not cached", summary.version());
282                }
283                IndexSummary::Unsupported(summary, schema_version) => {
284                    if let Some(rust_version) = summary.rust_version() {
285                        // HACK: technically its unsupported and we shouldn't make assumptions
286                        // about the entry but this is limited and for diagnostics purposes
287                        let _ = writeln!(
288                            &mut msg,
289                            "  version {} requires cargo {}",
290                            summary.version(),
291                            rust_version
292                        );
293                    } else {
294                        let _ = writeln!(
295                            &mut msg,
296                            "  version {} requires a Cargo version that supports index version {}",
297                            summary.version(),
298                            schema_version
299                        );
300                    }
301                }
302                IndexSummary::Invalid(summary) => {
303                    let _ = writeln!(
304                        &mut msg,
305                        "  version {}'s index entry is invalid",
306                        summary.version()
307                    );
308                }
309            }
310        }
311    } else if let Some(candidates) = alt_versions(registry, dep) {
312        let candidates = match candidates {
313            Ok(c) => c,
314            Err(e) => return to_resolve_err(e),
315        };
316        let versions = {
317            let mut versions = candidates
318                .iter()
319                .take(3)
320                .map(|cand| cand.version().to_string())
321                .collect::<Vec<_>>();
322
323            if candidates.len() > 3 {
324                versions.push("...".into());
325            }
326
327            versions.join(", ")
328        };
329
330        let locked_version = dep
331            .version_req()
332            .locked_version()
333            .map(|v| format!(" (locked to {})", v))
334            .unwrap_or_default();
335
336        let _ = writeln!(
337            &mut msg,
338            "failed to select a version for the requirement `{} = \"{}\"`{}",
339            dep.package_name(),
340            dep.version_req(),
341            locked_version,
342        );
343        let _ = writeln!(
344            &mut msg,
345            "candidate versions found which didn't match: {versions}",
346        );
347
348        // If we have a pre-release candidate, then that may be what our user is looking for
349        if let Some(pre) = candidates.iter().find(|c| c.version().is_prerelease()) {
350            let _ = write!(
351                &mut hints,
352                "\nhelp: if you are looking for the prerelease package it needs to be specified explicitly"
353            );
354            let _ = write!(
355                &mut hints,
356                "\n    {} = {{ version = \"{}\" }}",
357                pre.name(),
358                pre.version()
359            );
360        }
361
362        // If we have a path dependency with a locked version, then this may
363        // indicate that we updated a sub-package and forgot to run `cargo
364        // update`. In this case try to print a helpful error!
365        if dep.source_id().is_path() && dep.version_req().is_locked() {
366            let _ = write!(
367                &mut hints,
368                "\nhelp: to update a path dependency's locked version, run `cargo update`",
369            );
370        }
371
372        if registry.is_replaced(dep.source_id()) {
373            let _ = write!(
374                &mut hints,
375                "\nnote: perhaps a crate was updated and forgotten to be re-vendored?"
376            );
377        }
378    } else if let Some(name_candidates) = alt_names(registry, dep) {
379        let name_candidates = match name_candidates {
380            Ok(c) => c,
381            Err(e) => return to_resolve_err(e),
382        };
383        let _ = writeln!(
384            &mut msg,
385            "no matching package named `{}` found",
386            dep.package_name()
387        );
388
389        let mut names = name_candidates
390            .iter()
391            .take(3)
392            .map(|c| c.1.name().as_str())
393            .collect::<Vec<_>>();
394        if name_candidates.len() > 3 {
395            names.push("...");
396        }
397        let suggestions =
398            names
399                .iter()
400                .enumerate()
401                .fold(String::default(), |acc, (i, el)| match i {
402                    0 => acc + el,
403                    i if names.len() - 1 == i && name_candidates.len() <= 3 => acc + " or " + el,
404                    _ => acc + ", " + el,
405                });
406        let _ = writeln!(
407            &mut hints,
408            "\nhelp: packages with similar names: {suggestions}"
409        );
410    } else {
411        let _ = writeln!(
412            &mut msg,
413            "no matching package named `{}` found",
414            dep.package_name()
415        );
416    }
417
418    let mut location_searched_msg = registry.describe_source(dep.source_id());
419    if location_searched_msg.is_empty() {
420        location_searched_msg = format!("{}", dep.source_id());
421    }
422    let _ = writeln!(&mut msg, "location searched: {}", location_searched_msg);
423    let _ = write!(
424        &mut msg,
425        "required by {}",
426        describe_path_in_context(resolver_ctx, &parent.package_id()),
427    );
428
429    if has_too_new {
430        let downgrade_to =
431            alt_versions(registry, dep)
432                .and_then(|r| r.ok())
433                .and_then(|candidates| {
434                    candidates
435                        .into_iter()
436                        .find(|s| version_prefs.too_new(s).is_none())
437                });
438        if let Some(summary) = downgrade_to {
439            let _ = write!(
440                &mut hints,
441                "\nhelp: to preserve the min-publish-age, \
442                 downgrade the requirement to \"{}\"",
443                summary.version(),
444            );
445        }
446        let _ = write!(
447            &mut hints,
448            "\nhelp: to use too-new packages anyways, \
449             re-resolve with `CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow`",
450        );
451    }
452
453    if let Some(gctx) = gctx {
454        if let Some(offline_flag) = gctx.offline_flag() {
455            let _ = write!(
456                &mut hints,
457                "\nnote: offline mode (via `{offline_flag}`) \
458                 can sometimes cause surprising resolution failures\
459                 \nhelp: if this error is too confusing you may wish to retry \
460                 without `{offline_flag}`",
461            );
462        }
463    }
464
465    to_resolve_err(anyhow::format_err!("{msg}{hints}"))
466}
467
468// Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
469// was meant. So we re-query the registry with `dep="*"` so we can
470// list a few versions that were actually found.
471fn alt_versions(registry: &impl Registry, dep: &Dependency) -> Option<CargoResult<Vec<Summary>>> {
472    let mut wild_dep = dep.clone();
473    wild_dep.set_version_req(OptVersionReq::Any);
474
475    let candidates = match crate::util::block_on(registry.query_vec(&wild_dep, QueryKind::Exact)) {
476        Ok(candidates) => candidates,
477        Err(e) => return Some(Err(e)),
478    };
479    let mut candidates: Vec<_> = candidates
480        .into_iter()
481        .filter_map(|s| match s {
482            IndexSummary::Candidate(s) => Some(s),
483            _ => None,
484        })
485        .collect();
486    candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));
487    if candidates.is_empty() {
488        None
489    } else {
490        Some(Ok(candidates))
491    }
492}
493
494/// Maybe something is wrong with the available versions
495fn rejected_versions(
496    registry: &impl Registry,
497    dep: &Dependency,
498) -> Option<CargoResult<Vec<IndexSummary>>> {
499    let mut version_candidates =
500        match crate::util::block_on(registry.query_vec(&dep, QueryKind::RejectedVersions)) {
501            Ok(candidates) => candidates,
502            Err(e) => return Some(Err(e)),
503        };
504    version_candidates.sort_unstable_by_key(|a| a.package_id().version().clone());
505    if version_candidates.is_empty() {
506        None
507    } else {
508        Some(Ok(version_candidates))
509    }
510}
511
512/// Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
513/// was meant. So we try asking the registry for a `fuzzy` search for suggestions.
514fn alt_names(
515    registry: &impl Registry,
516    dep: &Dependency,
517) -> Option<CargoResult<Vec<(usize, Summary)>>> {
518    let mut wild_dep = dep.clone();
519    wild_dep.set_version_req(OptVersionReq::Any);
520
521    let name_candidates =
522        match crate::util::block_on(registry.query_vec(&wild_dep, QueryKind::AlternativeNames)) {
523            Ok(candidates) => candidates,
524            Err(e) => return Some(Err(e)),
525        };
526    let mut name_candidates: Vec<_> = name_candidates
527        .into_iter()
528        .map(|s| match s {
529            IndexSummary::Candidate(sum)
530            | IndexSummary::Yanked(sum)
531            | IndexSummary::Offline(sum)
532            | IndexSummary::Unsupported(sum, _)
533            | IndexSummary::Invalid(sum) => sum,
534        })
535        .collect();
536    name_candidates.sort_unstable_by_key(|a| a.name());
537    name_candidates.dedup_by(|a, b| a.name() == b.name());
538    let mut name_candidates: Vec<_> = name_candidates
539        .into_iter()
540        .filter_map(|n| Some((edit_distance(&*wild_dep.package_name(), &*n.name(), 3)?, n)))
541        .collect();
542    name_candidates.sort_by_key(|o| o.0);
543
544    if name_candidates.is_empty() {
545        None
546    } else {
547        Some(Ok(name_candidates))
548    }
549}
550
551/// Returns String representation of dependency chain for a particular `pkgid`
552/// within given context.
553pub(super) fn describe_path_in_context(cx: &ResolverContext, id: &PackageId) -> String {
554    let iter = cx
555        .parents
556        .path_to_bottom(id)
557        .into_iter()
558        .map(|(p, d)| (p, d.and_then(|d| d.iter().next())));
559    describe_path(iter)
560}
561
562/// Returns String representation of dependency chain for a particular `pkgid`.
563///
564/// Note that all elements of `path` iterator should have `Some` dependency
565/// except the first one. It would look like:
566///
567/// (pkg0, None)
568/// -> (pkg1, dep from pkg1 satisfied by pkg0)
569/// -> (pkg2, dep from pkg2 satisfied by pkg1)
570/// -> ...
571pub(crate) fn describe_path<'a>(
572    mut path: impl Iterator<Item = (&'a PackageId, Option<&'a Dependency>)>,
573) -> String {
574    use std::fmt::Write;
575
576    if let Some(p) = path.next() {
577        let mut dep_path_desc = format!("package `{}`", p.0);
578        for (pkg, dep) in path {
579            let dep = dep.unwrap();
580            let source_kind = if dep.source_id().is_path() {
581                "path "
582            } else if dep.source_id().is_git() {
583                "git "
584            } else {
585                ""
586            };
587            let requirement = if source_kind.is_empty() {
588                format!("{} = \"{}\"", dep.name_in_toml(), dep.version_req())
589            } else {
590                dep.name_in_toml().to_string()
591            };
592            let locked_version = dep
593                .version_req()
594                .locked_version()
595                .map(|v| format!("(locked to {}) ", v))
596                .unwrap_or_default();
597
598            write!(
599                dep_path_desc,
600                "\n    ... which satisfies {}dependency `{}` {}of package `{}`",
601                source_kind, requirement, locked_version, pkg
602            )
603            .unwrap();
604        }
605
606        return dep_path_desc;
607    }
608
609    String::new()
610}