#
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}