#
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                    }
    171                    // p == parent so the full path is redundant.
    172                }
    173                ConflictReason::RequiredDependencyAsFeature(feature) => {
    174                    msg.push_str("\n\npackage `");
    175                    msg.push_str(&*p.name());
    176                    msg.push_str("` depends on `");
    177                    msg.push_str(&*dep.package_name());
    178                    msg.push_str("` with feature `");
    179                    msg.push_str(feature);
    180                    msg.push_str("` but `");
    181                    msg.push_str(&*dep.package_name());
    182                    msg.push_str("` does not have that feature.\n");
    183                    msg.push_str(
    184                        " A required dependency with that name exists, \
    185                         but only optional dependencies can be used as features.\n",
    186                    );
    187                    // p == parent so the full path is redundant.
    188                }
    189                ConflictReason::NonImplicitDependencyAsFeature(feature) => {
    190                    msg.push_str("\n\npackage `");
    191                    msg.push_str(&*p.name());
    192                    msg.push_str("` depends on `");
    193                    msg.push_str(&*dep.package_name());
    194                    msg.push_str("` with feature `");
    195                    msg.push_str(feature);
    196                    msg.push_str("` but `");
    197                    msg.push_str(&*dep.package_name());
    198                    msg.push_str("` does not have that feature.\n");
    199                    msg.push_str(
    200                        " An optional dependency with that name exists, \
    201                         but that dependency uses the \"dep:\" \
    202                         syntax in the features table, so it does not have an \
    203                         implicit feature with that name.\n",
    204                    );
    205                    // p == parent so the full path is redundant.
    206                }
    207            }
    208        }
    209
    210        if has_semver {
    211            // Group these errors together.
    212            msg.push_str("\n\nall possible versions conflict with previously selected packages.");
    213            for (p, r) in &conflicting_activations {
    214                if let ConflictReason::Semver = r {
    215                    msg.push_str("\n\n  previously selected ");
    216                    msg.push_str(&describe_path_in_context(resolver_ctx, p));
    217                }
    218            }
    219        }
    220
    221        msg.push_str("\n\nfailed to select a version for `");
    222        msg.push_str(&*dep.package_name());
    223        msg.push_str("` which could resolve this conflict");
    224
    225        return to_resolve_err(anyhow::format_err!("{}", msg));
    226    }
    227
    228    // We didn't actually find any candidates, so we need to
    229    // give an error message that nothing was found.
    230    let mut msg = String::new();
    231    let mut hints = String::new();
    232    if let Some(version_candidates) = rejected_versions(registry, dep) {
    233        let version_candidates = match version_candidates {
    234            Ok(c) => c,
    235            Err(e) => return to_resolve_err(e),
    236        };
    237
    238        let locked_version = dep
    239            .version_req()
    240            .locked_version()
    241            .map(|v| format!(" (locked to {})", v))
    242            .unwrap_or_default();
    243        let _ = writeln!(
    244            &mut msg,
    245            "failed to select a version for the requirement `{} = \"{}\"`{}",
    246            dep.package_name(),
    247            dep.version_req(),
    248            locked_version
    249        );
    250        for candidate in version_candidates {
    251            match candidate {
    252                IndexSummary::Candidate(summary) => {
    253                    // HACK: If this was a real candidate, we wouldn't hit this case.
    254                    // so it must be a patch which get normalized to being a candidate
    255                    let _ = writeln!(&mut msg, "  version {} is unavailable", summary.version());
    256                }
    257                IndexSummary::Yanked(summary) => {
    258                    let _ = writeln!(&mut msg, "  version {} is yanked", summary.version());
    259                }
    260                IndexSummary::Offline(summary) => {
    261                    let _ = writeln!(&mut msg, "  version {} is not cached", summary.version());
    262                }
    263                IndexSummary::Unsupported(summary, schema_version) => {
    264                    if let Some(rust_version) = summary.rust_version() {
    265                        // HACK: technically its unsupported and we shouldn't make assumptions
    266                        // about the entry but this is limited and for diagnostics purposes
    267                        let _ = writeln!(
    268                            &mut msg,
    269                            "  version {} requires cargo {}",
    270                            summary.version(),
    271                            rust_version
    272                        );
    273                    } else {
    274                        let _ = writeln!(
    275                            &mut msg,
    276                            "  version {} requires a Cargo version that supports index version {}",
    277                            summary.version(),
    278                            schema_version
    279                        );
    280                    }
    281                }
    282                IndexSummary::Invalid(summary) => {
    283                    let _ = writeln!(
    284                        &mut msg,
    285                        "  version {}'s index entry is invalid",
    286                        summary.version()
    287                    );
    288                }
    289            }
    290        }
    291    } else if let Some(candidates) = alt_versions(registry, dep) {
    292        let candidates = match candidates {
    293            Ok(c) => c,
    294            Err(e) => return to_resolve_err(e),
    295        };
    296        let versions = {
    297            let mut versions = candidates
    298                .iter()
    299                .take(3)
    300                .map(|cand| cand.version().to_string())
    301                .collect::<Vec<_>>();
    302
    303            if candidates.len() > 3 {
    304                versions.push("...".into());
    305            }
    306
    307            versions.join(", ")
    308        };
    309
    310        let locked_version = dep
    311            .version_req()
    312            .locked_version()
    313            .map(|v| format!(" (locked to {})", v))
    314            .unwrap_or_default();
    315
    316        let _ = writeln!(
    317            &mut msg,
    318            "failed to select a version for the requirement `{} = \"{}\"`{}",
    319            dep.package_name(),
    320            dep.version_req(),
    321            locked_version,
    322        );
    323        let _ = writeln!(
    324            &mut msg,
    325            "candidate versions found which didn't match: {versions}",
    326        );
    327
    328        // If we have a pre-release candidate, then that may be what our user is looking for
    329        if let Some(pre) = candidates.iter().find(|c| c.version().is_prerelease()) {
    330            let _ = write!(
    331                &mut hints,
    332                "\nif you are looking for the prerelease package it needs to be specified explicitly"
    333            );
    334            let _ = write!(
    335                &mut hints,
    336                "\n    {} = {{ version = \"{}\" }}",
    337                pre.name(),
    338                pre.version()
    339            );
    340        }
    341
    342        // If we have a path dependency with a locked version, then this may
    343        // indicate that we updated a sub-package and forgot to run `cargo
    344        // update`. In this case try to print a helpful error!
    345        if dep.source_id().is_path() && dep.version_req().is_locked() {
    346            let _ = write!(
    347                &mut hints,
    348                "\nconsider running `cargo update` to update \
    349                          a path dependency's locked version",
    350            );
    351        }
    352
    353        if registry.is_replaced(dep.source_id()) {
    354            let _ = write!(
    355                &mut hints,
    356                "\nperhaps a crate was updated and forgotten to be re-vendored?"
    357            );
    358        }
    359    } else if let Some(name_candidates) = alt_names(registry, dep) {
    360        let name_candidates = match name_candidates {
    361            Ok(c) => c,
    362            Err(e) => return to_resolve_err(e),
    363        };
    364        let _ = writeln!(&mut msg, "no matching package found",);
    365        let _ = writeln!(&mut msg, "searched package name: `{}`", dep.package_name());
    366        let mut names = name_candidates
    367            .iter()
    368            .take(3)
    369            .map(|c| c.1.name().as_str())
    370            .collect::<Vec<_>>();
    371
    372        if name_candidates.len() > 3 {
    373            names.push("...");
    374        }
    375        // Vertically align first suggestion with missing crate name
    376        // so a typo jumps out at you.
    377        let suggestions =
    378            names
    379                .iter()
    380                .enumerate()
    381                .fold(String::default(), |acc, (i, el)| match i {
    382                    0 => acc + el,
    383                    i if names.len() - 1 == i && name_candidates.len() <= 3 => acc + " or " + el,
    384                    _ => acc + ", " + el,
    385                });
    386        let _ = writeln!(&mut msg, "perhaps you meant:      {suggestions}");
    387    } else {
    388        let _ = writeln!(
    389            &mut msg,
    390            "no matching package named `{}` found",
    391            dep.package_name()
    392        );
    393    }
    394
    395    let mut location_searched_msg = registry.describe_source(dep.source_id());
    396    if location_searched_msg.is_empty() {
    397        location_searched_msg = format!("{}", dep.source_id());
    398    }
    399    let _ = writeln!(&mut msg, "location searched: {}", location_searched_msg);
    400    let _ = write!(
    401        &mut msg,
    402        "required by {}",
    403        describe_path_in_context(resolver_ctx, &parent.package_id()),
    404    );
    405
    406    if let Some(gctx) = gctx {
    407        if let Some(offline_flag) = gctx.offline_flag() {
    408            let _ = write!(
    409                &mut hints,
    410                "\nAs a reminder, you're using offline mode ({offline_flag}) \
    411                 which can sometimes cause surprising resolution failures, \
    412                 if this error is too confusing you may wish to retry \
    413                 without `{offline_flag}`.",
    414            );
    415        }
    416    }
    417
    418    to_resolve_err(anyhow::format_err!("{msg}{hints}"))
    419}
    420
    421// Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
    422// was meant. So we re-query the registry with `dep="*"` so we can
    423// list a few versions that were actually found.
    424fn alt_versions(
    425    registry: &mut dyn Registry,
    426    dep: &Dependency,
    427) -> Option<CargoResult<Vec<Summary>>> {
    428    let mut wild_dep = dep.clone();
    429    wild_dep.set_version_req(OptVersionReq::Any);
    430
    431    let candidates = loop {
    432        match registry.query_vec(&wild_dep, QueryKind::Exact) {
    433            Poll::Ready(Ok(candidates)) => break candidates,
    434            Poll::Ready(Err(e)) => return Some(Err(e)),
    435            Poll::Pending => match registry.block_until_ready() {
    436                Ok(()) => continue,
    437                Err(e) => return Some(Err(e)),
    438            },
    439        }
    440    };
    441    let mut candidates: Vec<_> = candidates.into_iter().map(|s| s.into_summary()).collect();
    442    candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));
    443    if candidates.is_empty() {
    444        None
    445    } else {
    446        Some(Ok(candidates))
    447    }
    448}
    449
    450/// Maybe something is wrong with the available versions
    451fn rejected_versions(
    452    registry: &mut dyn Registry,
    453    dep: &Dependency,
    454) -> Option<CargoResult<Vec<IndexSummary>>> {
    455    let mut version_candidates = loop {
    456        match registry.query_vec(&dep, QueryKind::RejectedVersions) {
    457            Poll::Ready(Ok(candidates)) => break candidates,
    458            Poll::Ready(Err(e)) => return Some(Err(e)),
    459            Poll::Pending => match registry.block_until_ready() {
    460                Ok(()) => continue,
    461                Err(e) => return Some(Err(e)),
    462            },
    463        }
    464    };
    465    version_candidates.sort_unstable_by_key(|a| a.as_summary().version().clone());
    466    if version_candidates.is_empty() {
    467        None
    468    } else {
    469        Some(Ok(version_candidates))
    470    }
    471}
    472
    473/// Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
    474/// was meant. So we try asking the registry for a `fuzzy` search for suggestions.
    475fn alt_names(
    476    registry: &mut dyn Registry,
    477    dep: &Dependency,
    478) -> Option<CargoResult<Vec<(usize, Summary)>>> {
    479    let mut wild_dep = dep.clone();
    480    wild_dep.set_version_req(OptVersionReq::Any);
    481
    482    let name_candidates = loop {
    483        match registry.query_vec(&wild_dep, QueryKind::AlternativeNames) {
    484            Poll::Ready(Ok(candidates)) => break candidates,
    485            Poll::Ready(Err(e)) => return Some(Err(e)),
    486            Poll::Pending => match registry.block_until_ready() {
    487                Ok(()) => continue,
    488                Err(e) => return Some(Err(e)),
    489            },
    490        }
    491    };
    492    let mut name_candidates: Vec<_> = name_candidates
    493        .into_iter()
    494        .map(|s| s.into_summary())
    495        .collect();
    496    name_candidates.sort_unstable_by_key(|a| a.name());
    497    name_candidates.dedup_by(|a, b| a.name() == b.name());
    498    let mut name_candidates: Vec<_> = name_candidates
    499        .into_iter()
    500        .filter_map(|n| Some((edit_distance(&*wild_dep.package_name(), &*n.name(), 3)?, n)))
    501        .collect();
    502    name_candidates.sort_by_key(|o| o.0);
    503
    504    if name_candidates.is_empty() {
    505        None
    506    } else {
    507        Some(Ok(name_candidates))
    508    }
    509}
    510
    511/// Returns String representation of dependency chain for a particular `pkgid`
    512/// within given context.
    513pub(super) fn describe_path_in_context(cx: &ResolverContext, id: &PackageId) -> String {
    514    let iter = cx
    515        .parents
    516        .path_to_bottom(id)
    517        .into_iter()
    518        .map(|(p, d)| (p, d.and_then(|d| d.iter().next())));
    519    describe_path(iter)
    520}
    521
    522/// Returns String representation of dependency chain for a particular `pkgid`.
    523///
    524/// Note that all elements of `path` iterator should have `Some` dependency
    525/// except the first one. It would look like:
    526///
    527/// (pkg0, None)
    528/// -> (pkg1, dep from pkg1 satisfied by pkg0)
    529/// -> (pkg2, dep from pkg2 satisfied by pkg1)
    530/// -> ...
    531pub(crate) fn describe_path<'a>(
    532    mut path: impl Iterator<Item = (&'a PackageId, Option<&'a Dependency>)>,
    533) -> String {
    534    use std::fmt::Write;
    535
    536    if let Some(p) = path.next() {
    537        let mut dep_path_desc = format!("package `{}`", p.0);
    538        for (pkg, dep) in path {
    539            let dep = dep.unwrap();
    540            let source_kind = if dep.source_id().is_path() {
    541                "path "
    542            } else if dep.source_id().is_git() {
    543                "git "
    544            } else {
    545                ""
    546            };
    547            let requirement = if source_kind.is_empty() {
    548                format!("{} = \"{}\"", dep.name_in_toml(), dep.version_req())
    549            } else {
    550                dep.name_in_toml().to_string()
    551            };
    552            let locked_version = dep
    553                .version_req()
    554                .locked_version()
    555                .map(|v| format!("(locked to {}) ", v))
    556                .unwrap_or_default();
    557
    558            write!(
    559                dep_path_desc,
    560                "\n    ... which satisfies {}dependency `{}` {}of package `{}`",
    561                source_kind, requirement, locked_version, pkg
    562            )
    563            .unwrap();
    564        }
    565
    566        return dep_path_desc;
    567    }
    568
    569    String::new()
    570}