| //! There are 2 sources of facts for the resolver: |
| //! |
| //! - The `Registry` tells us for a `Dependency` what versions are available to fulfil it. |
| //! - The `Summary` tells us for a version (and features) what dependencies need to be fulfilled for it to be activated. |
| //! |
| //! These constitute immutable facts, the soled ground truth that all other inference depends on. |
| //! Theoretically this could all be enumerated ahead of time, but we want to be lazy and only |
| //! look up things we need to. The compromise is to cache the results as they are computed. |
| //! |
| //! This module impl that cache in all the gory details |
| |
| use crate::resolver::context::ResolverContext; |
| use crate::resolver::errors::describe_path_in_context; |
| use crate::resolver::types::{ConflictReason, DepInfo, FeaturesSet}; |
| use crate::resolver::{ |
| ActivateError, ActivateResult, CliFeatures, RequestedFeatures, ResolveOpts, VersionOrdering, |
| VersionPreferences, |
| }; |
| use crate::sources::IndexSummary; |
| use crate::sources::source::QueryKind; |
| use crate::util::LocalPollAdapter; |
| use crate::util::closest_msg; |
| use crate::util::errors::CargoResult; |
| use crate::util::interning::{INTERNED_DEFAULT, InternedString}; |
| use crate::workspace::{ |
| Dependency, FeatureValue, PackageId, PackageIdSpec, PackageIdSpecQuery, Registry, Summary, |
| }; |
| |
| use crate::util::data_structures::{HashMap, HashSet}; |
| use anyhow::Context as _; |
| use std::cell::RefCell; |
| use std::collections::BTreeSet; |
| use std::fmt::Write; |
| use std::rc::Rc; |
| use std::task::Poll; |
| use tracing::debug; |
| |
| pub struct RegistryQueryerAsync<'a, T: Registry> { |
| pub registry: &'a T, |
| replacements: &'a [(PackageIdSpec, Dependency)], |
| version_prefs: &'a VersionPreferences, |
| /// all the cases we ended up using a supplied replacement |
| used_replacements: RefCell<HashMap<PackageId, Summary>>, |
| } |
| |
| impl<'a, T: Registry> RegistryQueryerAsync<'a, T> { |
| pub fn new( |
| registry: &'a T, |
| replacements: &'a [(PackageIdSpec, Dependency)], |
| version_prefs: &'a VersionPreferences, |
| ) -> Self { |
| RegistryQueryerAsync { |
| registry, |
| replacements, |
| version_prefs, |
| used_replacements: RefCell::new(HashMap::default()), |
| } |
| } |
| |
| /// Queries the `registry` to return a list of candidates for `dep`. |
| /// |
| /// This method is the location where overrides are taken into account. If |
| /// any candidates are returned which match an override then the override is |
| /// applied by performing a second query for what the override should |
| /// return. |
| async fn query( |
| &self, |
| key: &(Dependency, Option<VersionOrdering>), |
| ) -> CargoResult<Rc<Vec<Summary>>> { |
| let (dep, first_version) = key; |
| let mut summaries = Vec::new(); |
| self.registry |
| .query(dep, QueryKind::Exact, &mut |s| match s { |
| IndexSummary::Candidate(summary) => { |
| // Filter out versions that are too new, |
| // unless pinned by a lock file or a `[patch]` entry. |
| // |
| // Unlike yanked, `cargo update --precise` does not opt in here |
| // unless `resolver.incompatible-publish-age = "allow"` is set. |
| let too_new = self.version_prefs.too_new(&summary).is_some(); |
| if !too_new || self.version_prefs.should_prefer(&summary.package_id()) { |
| summaries.push(summary); |
| } |
| } |
| // Prefer yanked only when |
| // |
| // * it is recorded in lock file or a `[patch]` entry |
| // * it is specified in `cargo update --precise` |
| IndexSummary::Yanked(summary) => { |
| let pkg_id = summary.package_id(); |
| let allow_precise = pkg_id |
| .source_id() |
| .precise_registry_version(pkg_id.name().as_str()) |
| .is_some_and(|(_, to)| to == pkg_id.version()); |
| if allow_precise || self.version_prefs.should_prefer(&pkg_id) { |
| summaries.push(summary); |
| } |
| } |
| _ => {} |
| }) |
| .await?; |
| |
| for summary in summaries.iter() { |
| let mut potential_matches = self |
| .replacements |
| .iter() |
| .filter(|(spec, _)| spec.matches(summary.package_id())); |
| |
| let Some((spec, dep)) = potential_matches.next() else { |
| continue; |
| }; |
| debug!( |
| "found an override for {} {}", |
| dep.package_name(), |
| dep.version_req() |
| ); |
| |
| let mut summaries = self |
| .registry |
| .query_vec(dep, QueryKind::Exact) |
| .await? |
| .into_iter() |
| .filter_map(|s| match s { |
| IndexSummary::Candidate(s) => Some(s), |
| _ => None, |
| }); |
| let s = summaries.next().ok_or_else(|| { |
| anyhow::format_err!( |
| "no matching package for override `{}` found\n\ |
| location searched: {}\n\ |
| version required: {}", |
| spec, |
| dep.source_id(), |
| dep.version_req() |
| ) |
| })?; |
| let summaries = summaries.collect::<Vec<_>>(); |
| if !summaries.is_empty() { |
| let bullets = summaries |
| .iter() |
| .map(|s| format!(" * {}", s.package_id())) |
| .collect::<Vec<_>>(); |
| return Err(anyhow::anyhow!( |
| "the replacement specification `{}` matched \ |
| multiple packages:\n * {}\n{}", |
| spec, |
| s.package_id(), |
| bullets.join("\n") |
| )); |
| } |
| |
| assert_eq!( |
| s.name(), |
| summary.name(), |
| "dependency should be hard coded to have the same name" |
| ); |
| if s.version() != summary.version() { |
| return Err(anyhow::anyhow!( |
| "replacement specification `{}` matched {} and tried to override it with {}\n\ |
| avoid matching unrelated packages by being more specific", |
| spec, |
| summary.version(), |
| s.version(), |
| )); |
| } |
| |
| let replace = if s.source_id() == summary.source_id() { |
| debug!("Preventing\n{:?}\nfrom replacing\n{:?}", summary, s); |
| None |
| } else { |
| Some(s) |
| }; |
| let matched_spec = spec.clone(); |
| |
| // Make sure no duplicates |
| if let Some((spec, _)) = potential_matches.next() { |
| return Err(anyhow::anyhow!( |
| "overlapping replacement specifications found:\n\n \ |
| * {}\n * {}\n\nboth specifications match: {}", |
| matched_spec, |
| spec, |
| summary.package_id() |
| )); |
| } |
| |
| for dep in summary.dependencies() { |
| debug!("\t{} => {}", dep.package_name(), dep.version_req()); |
| } |
| if let Some(r) = replace { |
| self.used_replacements |
| .borrow_mut() |
| .insert(summary.package_id(), r); |
| } |
| } |
| |
| self.version_prefs |
| .sort_summaries(&mut summaries, *first_version); |
| Ok(Rc::new(summaries)) |
| } |
| } |
| |
| /// Wrapper around RegistryQueryerAsync that provides |
| /// caching and a Poll based interface using `LocalPollAdapter`. |
| pub struct RegistryQueryer<'a, T: Registry> { |
| inner: Rc<RegistryQueryerAsync<'a, T>>, |
| poller: LocalPollAdapter< |
| 'a, |
| Rc<RegistryQueryerAsync<'a, T>>, |
| (Dependency, Option<VersionOrdering>), |
| CargoResult<Rc<Vec<Summary>>>, |
| >, |
| |
| /// a cache of `Dependency`s that are required for a `Summary` |
| /// |
| /// HACK: `first_version` is not kept in the cache key is it is 1:1 with |
| /// `parent.is_none()` (the first element of the cache key) as it doesn't change through |
| /// execution. |
| summary_cache: HashMap< |
| (Option<PackageId>, Summary, ResolveOpts), |
| (Rc<(HashSet<InternedString>, Rc<Vec<DepInfo>>)>, bool), |
| >, |
| } |
| |
| impl<'a, T: Registry> RegistryQueryer<'a, T> { |
| pub fn new( |
| registry: &'a T, |
| replacements: &'a [(PackageIdSpec, Dependency)], |
| version_prefs: &'a VersionPreferences, |
| ) -> Self { |
| let inner = Rc::new(RegistryQueryerAsync::new( |
| registry, |
| replacements, |
| version_prefs, |
| )); |
| Self { |
| inner: inner.clone(), |
| poller: LocalPollAdapter::new(inner), |
| summary_cache: HashMap::default(), |
| } |
| } |
| |
| pub fn registry(&self) -> &T { |
| self.inner.registry |
| } |
| |
| pub fn version_prefs(&self) -> &VersionPreferences { |
| self.inner.version_prefs |
| } |
| |
| pub fn query( |
| &mut self, |
| dep: &Dependency, |
| first_version: Option<VersionOrdering>, |
| ) -> Poll<CargoResult<Rc<Vec<Summary>>>> { |
| self.poller |
| .poll(RegistryQueryerAsync::query, (dep.clone(), first_version)) |
| } |
| |
| pub fn wait(&mut self) -> CargoResult<bool> { |
| let pending = self.poller.pending_count(); |
| // Have all outstanding registry requests been completed? |
| let mut all_ready = self.poller.wait(); |
| debug!(target: "cargo::resolver::restarting", pending); |
| |
| // Remove cached summaries that we produced with incomplete information. |
| self.summary_cache.retain(|_, (_, r)| { |
| if !*r { |
| all_ready = false; |
| } |
| *r |
| }); |
| |
| Ok(all_ready) |
| } |
| |
| pub fn used_replacement_for(&self, p: PackageId) -> Option<(PackageId, PackageId)> { |
| self.inner |
| .used_replacements |
| .borrow() |
| .get(&p) |
| .map(|r| (p, r.package_id())) |
| } |
| |
| pub fn replacement_summary(&self, p: PackageId) -> Option<Summary> { |
| self.inner.used_replacements.borrow().get(&p).cloned() |
| } |
| |
| /// Find out what dependencies will be added by activating `candidate`, |
| /// with features described in `opts`. Then look up in the `registry` |
| /// the candidates that will fulfil each of these dependencies, as it is the |
| /// next obvious question. |
| pub fn build_deps( |
| &mut self, |
| cx: &ResolverContext, |
| parent: Option<PackageId>, |
| candidate: &Summary, |
| opts: &ResolveOpts, |
| first_version: Option<VersionOrdering>, |
| ) -> ActivateResult<Rc<(HashSet<InternedString>, Rc<Vec<DepInfo>>)>> { |
| // if we have calculated a result before, then we can just return it, |
| // as it is a "pure" query of its arguments. |
| if let Some(out) = self |
| .summary_cache |
| .get(&(parent, candidate.clone(), opts.clone())) |
| { |
| return Ok(out.0.clone()); |
| } |
| // First, figure out our set of dependencies based on the requested set |
| // of features. This also calculates what features we're going to enable |
| // for our own dependencies. |
| let (used_features, deps) = resolve_features(parent, candidate, opts)?; |
| |
| // Next, transform all dependencies into a list of possible candidates |
| // which can satisfy that dependency. |
| let mut all_ready = true; |
| let mut deps = deps |
| .into_iter() |
| .filter_map(|(dep, features)| match self.query(&dep, first_version) { |
| Poll::Ready(Ok(candidates)) => Some(Ok((dep, candidates, features))), |
| Poll::Pending => { |
| all_ready = false; |
| // we can ignore Pending deps, resolve will be repeatedly called |
| // until there are none to ignore |
| None |
| } |
| Poll::Ready(Err(e)) => Some(Err(e).with_context(|| { |
| format!( |
| "failed to get `{}` as a dependency of {}", |
| dep.package_name(), |
| describe_path_in_context(cx, &candidate.package_id()), |
| ) |
| })), |
| }) |
| .collect::<CargoResult<Vec<DepInfo>>>()?; |
| |
| // Attempt to resolve dependencies with fewer candidates before trying |
| // dependencies with more candidates. This way if the dependency with |
| // only one candidate can't be resolved we don't have to do a bunch of |
| // work before we figure that out. |
| deps.sort_by_key(|(_, a, _)| a.len()); |
| |
| let out = Rc::new((used_features, Rc::new(deps))); |
| |
| // If we succeed we add the result to the cache so we can use it again next time. |
| // We don't cache the failure cases as they don't impl Clone. |
| self.summary_cache.insert( |
| (parent, candidate.clone(), opts.clone()), |
| (out.clone(), all_ready), |
| ); |
| |
| Ok(out) |
| } |
| } |
| |
| /// Returns the features we ended up using and |
| /// all dependencies and the features we want from each of them. |
| pub fn resolve_features<'b>( |
| parent: Option<PackageId>, |
| s: &'b Summary, |
| opts: &'b ResolveOpts, |
| ) -> ActivateResult<(HashSet<InternedString>, Vec<(Dependency, FeaturesSet)>)> { |
| // First, filter by dev-dependencies. |
| let deps = s.dependencies(); |
| let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps); |
| |
| let reqs = build_requirements(parent, s, opts)?; |
| let mut ret = Vec::new(); |
| let default_dep = BTreeSet::new(); |
| let mut valid_dep_names = HashSet::default(); |
| |
| // Next, collect all actually enabled dependencies and their features. |
| for dep in deps { |
| // Skip optional dependencies, but not those enabled through a |
| // feature |
| if dep.is_optional() && !reqs.deps.contains_key(&dep.name_in_toml()) { |
| continue; |
| } |
| valid_dep_names.insert(dep.name_in_toml()); |
| // So we want this dependency. Move the features we want from |
| // `feature_deps` to `ret` and register ourselves as using this |
| // name. |
| let mut base = reqs |
| .deps |
| .get(&dep.name_in_toml()) |
| .unwrap_or(&default_dep) |
| .clone(); |
| base.extend(dep.features().iter()); |
| ret.push((dep.clone(), Rc::new(base))); |
| } |
| |
| // This is a special case for command-line `--features |
| // dep_name/feat_name` where `dep_name` does not exist. All other |
| // validation is done either in `build_requirements` or |
| // `build_feature_map`. |
| if parent.is_none() { |
| for dep_name in reqs.deps.keys() { |
| if !valid_dep_names.contains(dep_name) { |
| let e = RequirementError::MissingDependency(*dep_name); |
| return Err(e.into_activate_error(parent, s)); |
| } |
| } |
| } |
| |
| Ok((reqs.into_features(), ret)) |
| } |
| |
| /// Takes requested features for a single package from the input `ResolveOpts` and |
| /// recurses to find all requested features, dependencies and requested |
| /// dependency features in a `Requirements` object, returning it to the resolver. |
| fn build_requirements<'a, 'b: 'a>( |
| parent: Option<PackageId>, |
| s: &'a Summary, |
| opts: &'b ResolveOpts, |
| ) -> ActivateResult<Requirements<'a>> { |
| let mut reqs = Requirements::new(s); |
| |
| let handle_default = |uses_default_features, reqs: &mut Requirements<'_>| { |
| if uses_default_features && s.features().contains_key("default") { |
| if let Err(e) = reqs.require_feature(INTERNED_DEFAULT) { |
| return Err(e.into_activate_error(parent, s)); |
| } |
| } |
| Ok(()) |
| }; |
| |
| match &opts.features { |
| RequestedFeatures::CliFeatures(CliFeatures { |
| features, |
| all_features, |
| uses_default_features, |
| }) => { |
| if *all_features { |
| for key in s.features().keys() { |
| if let Err(e) = reqs.require_feature(*key) { |
| return Err(e.into_activate_error(parent, s)); |
| } |
| } |
| } |
| |
| for fv in features.iter() { |
| if let Err(e) = reqs.require_value(fv) { |
| return Err(e.into_activate_error(parent, s)); |
| } |
| } |
| handle_default(*uses_default_features, &mut reqs)?; |
| } |
| RequestedFeatures::DepFeatures { |
| features, |
| uses_default_features, |
| } => { |
| for feature in features.iter() { |
| if let Err(e) = reqs.require_feature(*feature) { |
| return Err(e.into_activate_error(parent, s)); |
| } |
| } |
| handle_default(*uses_default_features, &mut reqs)?; |
| } |
| } |
| |
| Ok(reqs) |
| } |
| |
| /// Set of feature and dependency requirements for a package. |
| #[derive(Debug)] |
| struct Requirements<'a> { |
| summary: &'a Summary, |
| /// The deps map is a mapping of dependency name to list of features enabled. |
| /// |
| /// The resolver will activate all of these dependencies, with the given |
| /// features enabled. |
| deps: HashMap<InternedString, BTreeSet<InternedString>>, |
| /// The set of features enabled on this package which is later used when |
| /// compiling to instruct the code what features were enabled. |
| features: HashSet<InternedString>, |
| } |
| |
| /// An error for a requirement. |
| /// |
| /// This will later be converted to an `ActivateError` depending on whether or |
| /// not this is a dependency or a root package. |
| enum RequirementError { |
| /// The package does not have the requested feature. |
| MissingFeature(InternedString), |
| /// The package does not have the requested dependency. |
| MissingDependency(InternedString), |
| /// A feature has a direct cycle to itself. |
| /// |
| /// Note that cycles through multiple features are allowed (but perhaps |
| /// they shouldn't be?). |
| Cycle(InternedString), |
| } |
| |
| impl Requirements<'_> { |
| fn new(summary: &Summary) -> Requirements<'_> { |
| Requirements { |
| summary, |
| deps: HashMap::default(), |
| features: HashSet::default(), |
| } |
| } |
| |
| fn into_features(self) -> HashSet<InternedString> { |
| self.features |
| } |
| |
| fn require_dep_feature( |
| &mut self, |
| package: InternedString, |
| feat: InternedString, |
| weak: bool, |
| ) -> Result<(), RequirementError> { |
| // If `package` is indeed an optional dependency then we activate the |
| // feature named `package`, but otherwise if `package` is a required |
| // dependency then there's no feature associated with it. |
| if !weak |
| && self |
| .summary |
| .dependencies() |
| .iter() |
| .any(|dep| dep.name_in_toml() == package && dep.is_optional()) |
| { |
| // This optional dependency may not have an implicit feature of |
| // the same name if the `dep:` syntax is used to avoid creating |
| // that implicit feature. |
| if self.summary.features().contains_key(&package) { |
| self.require_feature(package)?; |
| } |
| } |
| self.deps.entry(package).or_default().insert(feat); |
| Ok(()) |
| } |
| |
| fn require_dependency(&mut self, pkg: InternedString) { |
| self.deps.entry(pkg).or_default(); |
| } |
| |
| fn require_feature(&mut self, feat: InternedString) -> Result<(), RequirementError> { |
| if !self.features.insert(feat) { |
| // Already seen this feature. |
| return Ok(()); |
| } |
| |
| let Some(fvs) = self.summary.features().get(&feat) else { |
| return Err(RequirementError::MissingFeature(feat)); |
| }; |
| |
| for fv in fvs { |
| if let FeatureValue::Feature(dep_feat) = fv { |
| if *dep_feat == feat { |
| return Err(RequirementError::Cycle(feat)); |
| } |
| } |
| self.require_value(fv)?; |
| } |
| Ok(()) |
| } |
| |
| fn require_value(&mut self, fv: &FeatureValue) -> Result<(), RequirementError> { |
| match fv { |
| FeatureValue::Feature(feat) => self.require_feature(*feat)?, |
| FeatureValue::Dep { dep_name } => self.require_dependency(*dep_name), |
| FeatureValue::DepFeature { |
| dep_name, |
| dep_feature, |
| // Weak features are always activated in the dependency |
| // resolver. They will be narrowed inside the new feature |
| // resolver. |
| weak, |
| } => self.require_dep_feature(*dep_name, *dep_feature, *weak)?, |
| }; |
| Ok(()) |
| } |
| } |
| |
| impl RequirementError { |
| fn into_activate_error(self, parent: Option<PackageId>, summary: &Summary) -> ActivateError { |
| match self { |
| RequirementError::MissingFeature(feat) => { |
| let deps: Vec<_> = summary |
| .dependencies() |
| .iter() |
| .filter(|dep| dep.name_in_toml() == feat) |
| .collect(); |
| if deps.is_empty() { |
| return match parent { |
| None => { |
| let closest = closest_msg( |
| &feat.as_str(), |
| summary.features().keys(), |
| |key| &key, |
| "feature", |
| ); |
| ActivateError::Fatal(anyhow::format_err!( |
| "package `{}` does not have the feature `{}`{}", |
| summary.package_id(), |
| feat, |
| closest |
| )) |
| } |
| Some(p) => ActivateError::Conflict(p, ConflictReason::MissingFeature(feat)), |
| }; |
| } |
| if deps.iter().any(|dep| dep.is_optional()) { |
| match parent { |
| None => { |
| let mut features = |
| features_enabling_dependency_sorted(summary, feat).peekable(); |
| let mut suggestion = String::new(); |
| if features.peek().is_some() { |
| suggestion = format!( |
| "\nDependency `{}` would be enabled by these features:", |
| feat |
| ); |
| for feature in (&mut features).take(3) { |
| let _ = write!(&mut suggestion, "\n\t- `{}`", feature); |
| } |
| if features.peek().is_some() { |
| suggestion.push_str("\n\t ..."); |
| } |
| } |
| ActivateError::Fatal(anyhow::format_err!( |
| "\ |
| package `{}` does not have feature `{}` |
| |
| help: an optional dependency \ |
| with that name exists, but the `features` table includes it with the \"dep:\" \ |
| syntax so it does not have an implicit feature with that name{}", |
| summary.package_id(), |
| feat, |
| suggestion |
| )) |
| } |
| Some(p) => ActivateError::Conflict( |
| p, |
| ConflictReason::NonImplicitDependencyAsFeature(feat), |
| ), |
| } |
| } else { |
| match parent { |
| None => ActivateError::Fatal(anyhow::format_err!( |
| "package `{}` does not have feature `{}` |
| |
| help: a dependency with that name exists but it is required dependency and only optional dependencies can be used as features.", |
| summary.package_id(), |
| feat, |
| )), |
| Some(p) => ActivateError::Conflict( |
| p, |
| ConflictReason::RequiredDependencyAsFeature(feat), |
| ), |
| } |
| } |
| } |
| RequirementError::MissingDependency(dep_name) => { |
| match parent { |
| None => ActivateError::Fatal(anyhow::format_err!( |
| "package `{}` does not have a dependency named `{}`", |
| summary.package_id(), |
| dep_name |
| )), |
| // This code path currently isn't used, since `foo/bar` |
| // and `dep:` syntax is not allowed in a dependency. |
| Some(p) => ActivateError::Conflict(p, ConflictReason::MissingFeature(dep_name)), |
| } |
| } |
| RequirementError::Cycle(feat) => ActivateError::Fatal(anyhow::format_err!( |
| "cyclic feature dependency: feature `{}` depends on itself", |
| feat |
| )), |
| } |
| } |
| } |
| |
| /// Collect any features which enable the optional dependency "target_dep". |
| /// |
| /// The returned value will be sorted. |
| fn features_enabling_dependency_sorted( |
| summary: &Summary, |
| target_dep: InternedString, |
| ) -> impl Iterator<Item = InternedString> + '_ { |
| let iter = summary |
| .features() |
| .iter() |
| .filter(move |(_, values)| { |
| for value in *values { |
| match value { |
| FeatureValue::Dep { dep_name } |
| | FeatureValue::DepFeature { |
| dep_name, |
| weak: false, |
| .. |
| } if dep_name == &target_dep => return true, |
| _ => (), |
| } |
| } |
| false |
| }) |
| .map(|(name, _)| *name); |
| // iter is already sorted because it was constructed from a BTreeMap. |
| iter |
| } |