| //! Resolution of the entire dependency graph for a crate. | |
| //! | |
| //! This module implements the core logic in taking the world of crates and | |
| //! constraints and creating a resolved graph with locked versions for all | |
| //! crates and their dependencies. This is separate from the registry module | |
| //! which is more worried about discovering crates from various sources, this | |
| //! module just uses the Registry trait as a source to learn about crates from. | |
| //! | |
| //! Actually solving a constraint graph is an NP-hard problem. This algorithm | |
| //! is basically a nice heuristic to make sure we get roughly the best answer | |
| //! most of the time. The constraints that we're working with are: | |
| //! | |
| //! 1. Each crate can have any number of dependencies. Each dependency can | |
| //! declare a version range that it is compatible with. | |
| //! 2. Crates can be activated with multiple version (e.g., show up in the | |
| //! dependency graph twice) so long as each pairwise instance have | |
| //! semver-incompatible versions. | |
| //! | |
| //! The algorithm employed here is fairly simple, we simply do a DFS, activating | |
| //! the "newest crate" (highest version) first and then going to the next | |
| //! option. The heuristics we employ are: | |
| //! | |
| //! * Never try to activate a crate version which is incompatible. This means we | |
| //! only try crates which will actually satisfy a dependency and we won't ever | |
| //! try to activate a crate that's semver compatible with something else | |
| //! activated (as we're only allowed to have one) nor try to activate a crate | |
| //! that has the same links attribute as something else | |
| //! activated. | |
| //! * Always try to activate the highest version crate first. The default | |
| //! dependency in Cargo (e.g., when you write `foo = "0.1.2"`) is | |
| //! semver-compatible, so selecting the highest version possible will allow us | |
| //! to hopefully satisfy as many dependencies at once. | |
| //! | |
| //! Beyond that, what's implemented below is just a naive backtracking version | |
| //! which should in theory try all possible combinations of dependencies and | |
| //! versions to see if one works. The first resolution that works causes | |
| //! everything to bail out immediately and return success, and only if *nothing* | |
| //! works do we actually return an error up the stack. | |
| //! | |
| //! Resolution is currently performed twice | |
| //! 1. With all features enabled (this is what gets saved to `Cargo.lock`) | |
| //! 2. With only the specific features the user selected on the command-line. Ideally this | |
| //! run will get removed in the future when transitioning to the new feature resolver. | |
| //! | |
| //! A new feature-specific resolver was added in 2020 which adds more sophisticated feature | |
| //! resolution. It is located in the [`features`] module. The original dependency resolver still | |
| //! performs feature unification, as it can help reduce the dependencies it has to consider during | |
| //! resolution (rather than assuming every optional dependency of every package is enabled). | |
| //! Checking if a feature is enabled must go through the new feature resolver. | |
| //! | |
| //! ## Performance | |
| //! | |
| //! Note that this is a relatively performance-critical portion of Cargo. The | |
| //! data that we're processing is proportional to the size of the dependency | |
| //! graph, which can often be quite large (e.g., take a look at Servo). To make | |
| //! matters worse the DFS algorithm we're implemented is inherently quite | |
| //! inefficient. When we add the requirement of backtracking on top it means | |
| //! that we're implementing something that probably shouldn't be allocating all | |
| //! over the place. | |
| use crate::util::data_structures::{HashMap, HashSet}; | |
| use rustc_hash::FxBuildHasher; | |
| use std::collections::BTreeMap; | |
| use std::rc::Rc; | |
| use std::time::{Duration, Instant}; | |
| use tracing::{debug, trace}; | |
| use crate::context::GlobalContext; | |
| use crate::util::errors::CargoResult; | |
| use crate::util::network::PollExt; | |
| use crate::workspace::PackageIdSpec; | |
| use crate::workspace::{Dependency, PackageId, Registry, Summary}; | |
| use self::context::ResolverContext; | |
| use self::dep_cache::RegistryQueryer; | |
| use self::features::RequestedFeatures; | |
| use self::types::{ConflictMap, ConflictReason, DepsFrame}; | |
| use self::types::{FeaturesSet, RcVecIter, RemainingDeps, ResolverProgress}; | |
| pub use self::errors::{ActivateError, ActivateResult, ResolveError}; | |
| pub use self::features::{CliFeatures, ForceAllTargets, HasDevUnits}; | |
| pub use self::resolve::{Resolve, ResolveVersion}; | |
| pub use self::types::{ResolveBehavior, ResolveOpts}; | |
| pub use self::version_prefs::PublishAgePolicy; | |
| pub use self::version_prefs::PublishAgeViolation; | |
| pub use self::version_prefs::VersionOrdering; | |
| pub use self::version_prefs::VersionPreferences; | |
| mod conflict_cache; | |
| mod context; | |
| mod dep_cache; | |
| pub(crate) mod encode; | |
| pub(crate) mod errors; | |
| pub mod features; | |
| mod resolve; | |
| mod types; | |
| mod version_prefs; | |
| /// Builds the list of all packages required to build the first argument. | |
| /// | |
| /// * `summaries` - the list of package summaries along with how to resolve | |
| /// their features. This is a list of all top-level packages that are intended | |
| /// to be part of the lock file (resolve output). These typically are a list | |
| /// of all workspace members. | |
| /// | |
| /// * `replacements` - this is a list of `[replace]` directives found in the | |
| /// root of the workspace. The list here is a `PackageIdSpec` of what to | |
| /// replace and a `Dependency` to replace that with. In general it's not | |
| /// recommended to use `[replace]` any more and use `[patch]` instead, which | |
| /// is supported elsewhere. | |
| /// | |
| /// * `registry` - this is the source from which all package summaries are | |
| /// loaded. It's expected that this is extensively configured ahead of time | |
| /// and is idempotent with our requests to it (aka returns the same results | |
| /// for the same query every time). Typically this is an instance of a | |
| /// `PackageRegistry`. | |
| /// | |
| /// * `version_prefs` - this represents a preference for some versions over others, | |
| /// based on the lock file or other reasons such as `[patch]`es. | |
| /// | |
| /// * `resolve_version` - this controls how the lockfile will be serialized. | |
| /// | |
| /// * `gctx` - the global context used for configuration and diagnostics. | |
| #[tracing::instrument(skip_all)] | |
| pub fn resolve( | |
| summaries: &[(Summary, ResolveOpts)], | |
| replacements: &[(PackageIdSpec, Dependency)], | |
| registry: &impl Registry, | |
| version_prefs: &VersionPreferences, | |
| resolve_version: ResolveVersion, | |
| gctx: &GlobalContext, | |
| ) -> CargoResult<Resolve> { | |
| let first_version = gctx | |
| .cli_unstable() | |
| .direct_minimal_versions | |
| .then_some(VersionOrdering::MinimumVersionsFirst); | |
| let mut registry = RegistryQueryer::new(registry, replacements, version_prefs); | |
| // Global cache of the reasons for each time we backtrack. | |
| let mut past_conflicting_activations = conflict_cache::ConflictCache::new(); | |
| let resolver_ctx = loop { | |
| let resolver_ctx = activate_deps_loop( | |
| &mut registry, | |
| summaries, | |
| first_version, | |
| gctx, | |
| &mut past_conflicting_activations, | |
| )?; | |
| if registry.wait()? { | |
| break resolver_ctx; | |
| } | |
| }; | |
| let mut cksums = HashMap::default(); | |
| for (summary, _) in resolver_ctx.activations.values() { | |
| let cksum = summary.checksum().map(|s| s.to_string()); | |
| cksums.insert(summary.package_id(), cksum); | |
| } | |
| let graph = resolver_ctx.graph(); | |
| let replacements = resolver_ctx.resolve_replacements(®istry); | |
| let features = resolver_ctx | |
| .resolve_features | |
| .iter() | |
| .map(|(k, v)| (*k, v.iter().cloned().collect())) | |
| .collect(); | |
| let summaries = resolver_ctx | |
| .activations | |
| .into_iter() | |
| .map(|(_key, (summary, _age))| (summary.package_id(), summary)) | |
| .collect(); | |
| let resolve = Resolve::new( | |
| graph, | |
| replacements, | |
| features, | |
| cksums, | |
| BTreeMap::new(), | |
| Vec::new(), | |
| resolve_version, | |
| summaries, | |
| ); | |
| check_cycles(&resolve)?; | |
| check_duplicate_pkgs_in_lockfile(&resolve)?; | |
| trace!("resolved: {:?}", resolve); | |
| Ok(resolve) | |
| } | |
| /// Recursively activates the dependencies for `summaries`, in depth-first order, | |
| /// backtracking across possible candidates for each dependency as necessary. | |
| /// | |
| /// If all dependencies can be activated and resolved to a version in the | |
| /// dependency graph, `cx` is returned. | |
| fn activate_deps_loop( | |
| registry: &mut RegistryQueryer<'_, impl Registry>, | |
| summaries: &[(Summary, ResolveOpts)], | |
| first_version: Option<VersionOrdering>, | |
| gctx: &GlobalContext, | |
| past_conflicting_activations: &mut conflict_cache::ConflictCache, | |
| ) -> CargoResult<ResolverContext> { | |
| let mut resolver_ctx = ResolverContext::new(); | |
| let mut backtrack_stack = Vec::new(); | |
| let mut remaining_deps = RemainingDeps::new(); | |
| // Activate all the initial summaries to kick off some work. | |
| for (summary, opts) in summaries { | |
| debug!("initial activation: {}", summary.package_id()); | |
| let res = activate( | |
| &mut resolver_ctx, | |
| registry, | |
| None, | |
| summary.clone(), | |
| first_version, | |
| opts, | |
| ); | |
| match res { | |
| Ok(Some((frame, _))) => remaining_deps.push(frame), | |
| Ok(None) => (), | |
| Err(ActivateError::Fatal(e)) => return Err(e), | |
| Err(ActivateError::Conflict(_, _)) => panic!("bad error from activate"), | |
| } | |
| } | |
| let mut printed = ResolverProgress::new(); | |
| // Main resolution loop, this is the workhorse of the resolution algorithm. | |
| // | |
| // You'll note that a few stacks are maintained on the side, which might | |
| // seem odd when this algorithm looks like it could be implemented | |
| // recursively. While correct, this is implemented iteratively to avoid | |
| // blowing the stack (the recursion depth is proportional to the size of the | |
| // input). | |
| // | |
| // The general sketch of this loop is to run until there are no dependencies | |
| // left to activate, and for each dependency to attempt to activate all of | |
| // its own dependencies in turn. The `backtrack_stack` is a side table of | |
| // backtracking states where if we hit an error we can return to in order to | |
| // attempt to continue resolving. | |
| while let Some((just_here_for_the_error_messages, frame)) = | |
| remaining_deps.pop_most_constrained() | |
| { | |
| let (mut parent, (mut dep, candidates, mut features)) = frame; | |
| // If we spend a lot of time here (we shouldn't in most cases) then give | |
| // a bit of a visual indicator as to what we're doing. | |
| printed.shell_status(gctx)?; | |
| trace!( | |
| "{}[{}]>{} {} candidates", | |
| parent.name(), | |
| resolver_ctx.age, | |
| dep.package_name(), | |
| candidates.len() | |
| ); | |
| let just_here_for_the_error_messages = just_here_for_the_error_messages | |
| && past_conflicting_activations | |
| .conflicting(&resolver_ctx, &dep) | |
| .is_some(); | |
| let mut remaining_candidates = RemainingCandidates::new(&candidates); | |
| // `conflicting_activations` stores all the reasons we were unable to | |
| // activate candidates. One of these reasons will have to go away for | |
| // backtracking to find a place to restart. It is also the list of | |
| // things to explain in the error message if we fail to resolve. | |
| // | |
| // This is a map of package ID to a reason why that packaged caused a | |
| // conflict for us. | |
| let mut conflicting_activations = ConflictMap::new(); | |
| // When backtracking we don't fully update `conflicting_activations` | |
| // especially for the cases that we didn't make a backtrack frame in the | |
| // first place. This `backtracked` var stores whether we are continuing | |
| // from a restored backtrack frame so that we can skip caching | |
| // `conflicting_activations` in `past_conflicting_activations` | |
| let mut backtracked = false; | |
| loop { | |
| let next = remaining_candidates.next(&mut conflicting_activations, &resolver_ctx); | |
| let (candidate, has_another) = next.ok_or(()).or_else(|_| { | |
| // If we get here then our `remaining_candidates` was just | |
| // exhausted, so `dep` failed to activate. | |
| // | |
| // It's our job here to backtrack, if possible, and find a | |
| // different candidate to activate. If we can't find any | |
| // candidates whatsoever then it's time to bail entirely. | |
| trace!( | |
| "{}[{}]>{} -- no candidates", | |
| parent.name(), | |
| resolver_ctx.age, | |
| dep.package_name() | |
| ); | |
| // Use our list of `conflicting_activations` to add to our | |
| // global list of past conflicting activations, effectively | |
| // globally poisoning `dep` if `conflicting_activations` ever | |
| // shows up again. We'll use the `past_conflicting_activations` | |
| // below to determine if a dependency is poisoned and skip as | |
| // much work as possible. | |
| // | |
| // If we're only here for the error messages then there's no | |
| // need to try this as this dependency is already known to be | |
| // bad. | |
| // | |
| // As we mentioned above with the `backtracked` variable if this | |
| // local is set to `true` then our `conflicting_activations` may | |
| // not be right, so we can't push into our global cache. | |
| let mut generalize_conflicting_activations = None; | |
| if !just_here_for_the_error_messages && !backtracked { | |
| past_conflicting_activations.insert(&dep, &conflicting_activations); | |
| if let Some(c) = generalize_conflicting( | |
| &resolver_ctx, | |
| registry, | |
| past_conflicting_activations, | |
| &parent, | |
| &dep, | |
| &conflicting_activations, | |
| ) { | |
| generalize_conflicting_activations = Some(c); | |
| } | |
| } | |
| match find_candidate( | |
| &resolver_ctx, | |
| &mut backtrack_stack, | |
| &parent, | |
| backtracked, | |
| generalize_conflicting_activations | |
| .as_ref() | |
| .unwrap_or(&conflicting_activations), | |
| ) { | |
| Some((candidate, has_another, frame)) => { | |
| // Reset all of our local variables used with the | |
| // contents of `frame` to complete our backtrack. | |
| resolver_ctx = frame.context; | |
| remaining_deps = frame.remaining_deps; | |
| remaining_candidates = frame.remaining_candidates; | |
| parent = frame.parent; | |
| dep = frame.dep; | |
| features = frame.features; | |
| conflicting_activations = frame.conflicting_activations; | |
| backtracked = true; | |
| Ok((candidate, has_another)) | |
| } | |
| None => { | |
| debug!("no candidates found"); | |
| Err(errors::activation_error( | |
| &resolver_ctx, | |
| registry.registry(), | |
| registry.version_prefs(), | |
| &parent, | |
| &dep, | |
| &conflicting_activations, | |
| &candidates, | |
| gctx, | |
| )) | |
| } | |
| } | |
| })?; | |
| // If we're only here for the error messages then we know that this | |
| // activation will fail one way or another. To that end if we've got | |
| // more candidates we want to fast-forward to the last one as | |
| // otherwise we'll just backtrack here anyway (helping us to skip | |
| // some work). | |
| if just_here_for_the_error_messages && !backtracked && has_another { | |
| continue; | |
| } | |
| // We have a `candidate`. Create a `BacktrackFrame` so we can add it | |
| // to the `backtrack_stack` later if activation succeeds. | |
| // | |
| // Note that if we don't actually have another candidate then there | |
| // will be nothing to backtrack to so we skip construction of the | |
| // frame. This is a relatively important optimization as a number of | |
| // the `clone` calls below can be quite expensive, so we avoid them | |
| // if we can. | |
| let backtrack = if has_another { | |
| Some(BacktrackFrame { | |
| context: ResolverContext::clone(&resolver_ctx), | |
| remaining_deps: remaining_deps.clone(), | |
| remaining_candidates: remaining_candidates.clone(), | |
| parent: Summary::clone(&parent), | |
| dep: Dependency::clone(&dep), | |
| features: Rc::clone(&features), | |
| conflicting_activations: conflicting_activations.clone(), | |
| }) | |
| } else { | |
| None | |
| }; | |
| let pid = candidate.package_id(); | |
| let opts = ResolveOpts { | |
| dev_deps: false, | |
| features: RequestedFeatures::DepFeatures { | |
| features: Rc::clone(&features), | |
| uses_default_features: dep.uses_default_features(), | |
| }, | |
| }; | |
| trace!( | |
| "{}[{}]>{} trying {}", | |
| parent.name(), | |
| resolver_ctx.age, | |
| dep.package_name(), | |
| candidate.version() | |
| ); | |
| let first_version = None; // this is an indirect dependency | |
| let res = activate( | |
| &mut resolver_ctx, | |
| registry, | |
| Some((&parent, &dep)), | |
| candidate, | |
| first_version, | |
| &opts, | |
| ); | |
| let successfully_activated = match res { | |
| // Success! We've now activated our `candidate` in our context | |
| // and we're almost ready to move on. We may want to scrap this | |
| // frame in the end if it looks like it's not going to end well, | |
| // so figure that out here. | |
| Ok(Some((mut frame, dur))) => { | |
| printed.elapsed(dur); | |
| // Our `frame` here is a new package with its own list of | |
| // dependencies. Do a sanity check here of all those | |
| // dependencies by cross-referencing our global | |
| // `past_conflicting_activations`. Recall that map is a | |
| // global cache which lists sets of packages where, when | |
| // activated, the dependency is unresolvable. | |
| // | |
| // If any our frame's dependencies fit in that bucket, | |
| // aka known unresolvable, then we extend our own set of | |
| // conflicting activations with theirs. We can do this | |
| // because the set of conflicts we found implies the | |
| // dependency can't be activated which implies that we | |
| // ourselves can't be activated, so we know that they | |
| // conflict with us. | |
| let mut has_past_conflicting_dep = just_here_for_the_error_messages; | |
| if !has_past_conflicting_dep { | |
| if let Some(conflicting) = | |
| frame | |
| .remaining_siblings | |
| .remaining() | |
| .find_map(|(new_dep, _, _)| { | |
| past_conflicting_activations.conflicting(&resolver_ctx, new_dep) | |
| }) | |
| { | |
| // If one of our deps is known unresolvable | |
| // then we will not succeed. | |
| // How ever if we are part of the reason that | |
| // one of our deps conflicts then | |
| // we can make a stronger statement | |
| // because we will definitely be activated when | |
| // we try our dep. | |
| conflicting_activations.extend( | |
| conflicting | |
| .iter() | |
| .filter(|&(p, _)| p != &pid) | |
| .map(|(&p, r)| (p, r.clone())), | |
| ); | |
| has_past_conflicting_dep = true; | |
| } | |
| } | |
| // If any of `remaining_deps` are known unresolvable with | |
| // us activated, then we extend our own set of | |
| // conflicting activations with theirs and its parent. We can do this | |
| // because the set of conflicts we found implies the | |
| // dependency can't be activated which implies that we | |
| // ourselves are incompatible with that dep, so we know that deps | |
| // parent conflict with us. | |
| if !has_past_conflicting_dep { | |
| if let Some(known_related_bad_deps) = | |
| past_conflicting_activations.dependencies_conflicting_with(pid) | |
| { | |
| if let Some((other_parent, conflict)) = remaining_deps | |
| .iter() | |
| // for deps related to us | |
| .filter(|(_, other_dep)| known_related_bad_deps.contains(other_dep)) | |
| .filter_map(|(other_parent, other_dep)| { | |
| past_conflicting_activations | |
| .find_conflicting(&resolver_ctx, &other_dep, Some(pid)) | |
| .map(|con| (other_parent, con)) | |
| }) | |
| .next() | |
| { | |
| let rel = conflict.get(&pid).unwrap().clone(); | |
| // The conflict we found is | |
| // "other dep will not succeed if we are activated." | |
| // We want to add | |
| // "our dep will not succeed if other dep is in remaining_deps" | |
| // but that is not how the cache is set up. | |
| // So we add the less general but much faster, | |
| // "our dep will not succeed if other dep's parent is activated". | |
| conflicting_activations.extend( | |
| conflict | |
| .iter() | |
| .filter(|&(p, _)| p != &pid) | |
| .map(|(&p, r)| (p, r.clone())), | |
| ); | |
| conflicting_activations.insert(other_parent, rel); | |
| has_past_conflicting_dep = true; | |
| } | |
| } | |
| } | |
| // Ok if we're in a "known failure" state for this frame we | |
| // may want to skip it altogether though. We don't want to | |
| // skip it though in the case that we're displaying error | |
| // messages to the user! | |
| // | |
| // Here we need to figure out if the user will see if we | |
| // skipped this candidate (if it's known to fail, aka has a | |
| // conflicting dep and we're the last candidate). If we're | |
| // here for the error messages, we can't skip it (but we can | |
| // prune extra work). If we don't have any candidates in our | |
| // backtrack stack then we're the last line of defense, so | |
| // we'll want to present an error message for sure. | |
| let activate_for_error_message = has_past_conflicting_dep && !has_another && { | |
| just_here_for_the_error_messages || { | |
| find_candidate( | |
| &resolver_ctx, | |
| &mut backtrack_stack.clone(), | |
| &parent, | |
| backtracked, | |
| &conflicting_activations, | |
| ) | |
| .is_none() | |
| } | |
| }; | |
| // If we're only here for the error messages then we know | |
| // one of our candidate deps will fail, meaning we will | |
| // fail and that none of the backtrack frames will find a | |
| // candidate that will help. Consequently let's clean up the | |
| // no longer needed backtrack frames. | |
| if activate_for_error_message { | |
| backtrack_stack.clear(); | |
| } | |
| // If we don't know for a fact that we'll fail or if we're | |
| // just here for the error message then we push this frame | |
| // onto our list of to-be-resolve, which will generate more | |
| // work for us later on. | |
| // | |
| // Otherwise we're guaranteed to fail and were not here for | |
| // error messages, so we skip work and don't push anything | |
| // onto our stack. | |
| frame.just_for_error_messages = has_past_conflicting_dep; | |
| if !has_past_conflicting_dep || activate_for_error_message { | |
| remaining_deps.push(frame); | |
| true | |
| } else { | |
| trace!( | |
| "{}[{}]>{} skipping {} ", | |
| parent.name(), | |
| resolver_ctx.age, | |
| dep.package_name(), | |
| pid.version() | |
| ); | |
| false | |
| } | |
| } | |
| // This candidate's already activated, so there's no extra work | |
| // for us to do. Let's keep going. | |
| Ok(None) => true, | |
| // We failed with a super fatal error (like a network error), so | |
| // bail out as quickly as possible as we can't reliably | |
| // backtrack from errors like these | |
| Err(ActivateError::Fatal(e)) => return Err(e), | |
| // We failed due to a bland conflict, bah! Record this in our | |
| // frame's list of conflicting activations as to why this | |
| // candidate failed, and then move on. | |
| Err(ActivateError::Conflict(id, reason)) => { | |
| conflicting_activations.insert(id, reason); | |
| false | |
| } | |
| }; | |
| // If we've successfully activated then save off the backtrack frame | |
| // if one was created, and otherwise break out of the inner | |
| // activation loop as we're ready to move to the next dependency | |
| if successfully_activated { | |
| backtrack_stack.extend(backtrack); | |
| break; | |
| } | |
| // We've failed to activate this dependency, oh dear! Our call to | |
| // `activate` above may have altered our `cx` local variable, so | |
| // restore it back if we've got a backtrack frame. | |
| // | |
| // If we don't have a backtrack frame then we're just using the `cx` | |
| // for error messages anyway so we can live with a little | |
| // imprecision. | |
| if let Some(b) = backtrack { | |
| resolver_ctx = b.context; | |
| } | |
| } | |
| // Ok phew, that loop was a big one! If we've broken out then we've | |
| // successfully activated a candidate. Our stacks are all in place that | |
| // we're ready to move on to the next dependency that needs activation, | |
| // so loop back to the top of the function here. | |
| } | |
| Ok(resolver_ctx) | |
| } | |
| /// Attempts to activate the summary `candidate` in the context `cx`. | |
| /// | |
| /// This function will pull dependency summaries from the registry provided, and | |
| /// the dependencies of the package will be determined by the `opts` provided. | |
| /// If `candidate` was activated, this function returns the dependency frame to | |
| /// iterate through next. | |
| fn activate( | |
| cx: &mut ResolverContext, | |
| registry: &mut RegistryQueryer<'_, impl Registry>, | |
| parent: Option<(&Summary, &Dependency)>, | |
| candidate: Summary, | |
| first_version: Option<VersionOrdering>, | |
| opts: &ResolveOpts, | |
| ) -> ActivateResult<Option<(DepsFrame, Duration)>> { | |
| let candidate_pid = candidate.package_id(); | |
| cx.age += 1; | |
| if let Some((parent, dep)) = parent { | |
| let parent_pid = parent.package_id(); | |
| // add an edge from candidate to parent in the parents graph | |
| cx.parents | |
| .link(candidate_pid, parent_pid) | |
| // and associate dep with that edge | |
| .insert(dep.clone()); | |
| } | |
| let activated = cx.flag_activated(&candidate, opts, parent)?; | |
| let candidate = match registry.replacement_summary(candidate_pid) { | |
| Some(replace) => { | |
| // Note the `None` for parent here since `[replace]` is a bit wonky | |
| // and doesn't activate the same things that `[patch]` typically | |
| // does. TBH it basically cause panics in the test suite if | |
| // `parent` is passed through here and `[replace]` is otherwise | |
| // on life support so it's not critical to fix bugs anyway per se. | |
| if cx.flag_activated(&replace, opts, None)? && activated { | |
| return Ok(None); | |
| } | |
| trace!( | |
| "activating {} (replacing {})", | |
| replace.package_id(), | |
| candidate_pid | |
| ); | |
| replace.clone() | |
| } | |
| None => { | |
| if activated { | |
| return Ok(None); | |
| } | |
| trace!("activating {}", candidate_pid); | |
| candidate | |
| } | |
| }; | |
| let now = Instant::now(); | |
| let (used_features, deps) = &*registry.build_deps( | |
| cx, | |
| parent.map(|p| p.0.package_id()), | |
| &candidate, | |
| opts, | |
| first_version, | |
| )?; | |
| // Record what list of features is active for this package. | |
| if !used_features.is_empty() { | |
| Rc::make_mut( | |
| cx.resolve_features | |
| .entry(candidate.package_id()) | |
| .or_default(), | |
| ) | |
| .extend(used_features); | |
| } | |
| let frame = DepsFrame { | |
| parent: candidate, | |
| just_for_error_messages: false, | |
| remaining_siblings: RcVecIter::new(Rc::clone(deps)), | |
| }; | |
| Ok(Some((frame, now.elapsed()))) | |
| } | |
| #[derive(Clone)] | |
| struct BacktrackFrame { | |
| context: ResolverContext, | |
| remaining_deps: RemainingDeps, | |
| remaining_candidates: RemainingCandidates, | |
| parent: Summary, | |
| dep: Dependency, | |
| features: FeaturesSet, | |
| conflicting_activations: ConflictMap, | |
| } | |
| /// A helper "iterator" used to extract candidates within a current `Context` of | |
| /// a dependency graph. | |
| /// | |
| /// This struct doesn't literally implement the `Iterator` trait (requires a few | |
| /// more inputs) but in general acts like one. Each `RemainingCandidates` is | |
| /// created with a list of candidates to choose from. When attempting to iterate | |
| /// over the list of candidates only *valid* candidates are returned. Validity | |
| /// is defined within a `Context`. | |
| /// | |
| /// Candidates passed to `new` may not be returned from `next` as they could be | |
| /// filtered out, and as they are filtered the causes will be added to `conflicting_prev_active`. | |
| #[derive(Clone)] | |
| struct RemainingCandidates { | |
| remaining: RcVecIter<Summary>, | |
| // This is an inlined peekable generator | |
| has_another: Option<Summary>, | |
| } | |
| impl RemainingCandidates { | |
| fn new(candidates: &Rc<Vec<Summary>>) -> RemainingCandidates { | |
| RemainingCandidates { | |
| remaining: RcVecIter::new(Rc::clone(candidates)), | |
| has_another: None, | |
| } | |
| } | |
| /// Attempts to find another candidate to check from this list. | |
| /// | |
| /// This method will attempt to move this iterator forward, returning a | |
| /// candidate that's possible to activate. The `cx` argument is the current | |
| /// context which determines validity for candidates returned, and the `dep` | |
| /// is the dependency listing that we're activating for. | |
| /// | |
| /// If successful a `(Candidate, bool)` pair will be returned. The | |
| /// `Candidate` is the candidate to attempt to activate, and the `bool` is | |
| /// an indicator of whether there are remaining candidates to try of if | |
| /// we've reached the end of iteration. | |
| /// | |
| /// If we've reached the end of the iterator here then `Err` will be | |
| /// returned. The error will contain a map of package ID to conflict reason, | |
| /// where each package ID caused a candidate to be filtered out from the | |
| /// original list for the reason listed. | |
| fn next( | |
| &mut self, | |
| conflicting_prev_active: &mut ConflictMap, | |
| cx: &ResolverContext, | |
| ) -> Option<(Summary, bool)> { | |
| for b in self.remaining.iter() { | |
| let b_id = b.package_id(); | |
| // The condition for being a valid candidate relies on | |
| // semver. Cargo dictates that you can't duplicate multiple | |
| // semver-compatible versions of a crate. For example we can't | |
| // simultaneously activate `foo 1.0.2` and `foo 1.2.0`. We can, | |
| // however, activate `1.0.2` and `2.0.0`. | |
| // | |
| // Here we throw out our candidate if it's *compatible*, yet not | |
| // equal, to all previously activated versions. | |
| if let Some((a, _)) = cx.activations.get(&b_id.as_activations_key()) { | |
| if a != b { | |
| conflicting_prev_active | |
| .entry(a.package_id()) | |
| .or_insert(ConflictReason::Semver); | |
| continue; | |
| } | |
| } | |
| // Otherwise the `links` key in the manifest dictates that there's only one | |
| // package in a dependency graph, globally, with that particular | |
| // `links` key. If this candidate links to something that's already | |
| // linked to by a different package then we've gotta skip this. | |
| if let Some(link) = b.links() { | |
| if let Some(&a) = cx.links.get(&link) { | |
| if a != b_id { | |
| conflicting_prev_active | |
| .entry(a) | |
| .or_insert_with(|| ConflictReason::Links(link)); | |
| continue; | |
| } | |
| } | |
| } | |
| // Well if we made it this far then we've got a valid dependency. We | |
| // want this iterator to be inherently "peekable" so we don't | |
| // necessarily return the item just yet. Instead we stash it away to | |
| // get returned later, and if we replaced something then that was | |
| // actually the candidate to try first so we return that. | |
| if let Some(r) = self.has_another.replace(b.clone()) { | |
| return Some((r, true)); | |
| } | |
| } | |
| // Alright we've entirely exhausted our list of candidates. If we've got | |
| // something stashed away return that here (also indicating that there's | |
| // nothing else). | |
| self.has_another.take().map(|r| (r, false)) | |
| } | |
| } | |
| /// Attempts to find a new conflict that allows a `find_candidate` better then the input one. | |
| /// It will add the new conflict to the cache if one is found. | |
| fn generalize_conflicting( | |
| cx: &ResolverContext, | |
| registry: &mut RegistryQueryer<'_, impl Registry>, | |
| past_conflicting_activations: &mut conflict_cache::ConflictCache, | |
| parent: &Summary, | |
| dep: &Dependency, | |
| conflicting_activations: &ConflictMap, | |
| ) -> Option<ConflictMap> { | |
| // We need to determine the `ContextAge` that this `conflicting_activations` will jump to, and why. | |
| let (backtrack_critical_age, backtrack_critical_id) = shortcircuit_max( | |
| conflicting_activations | |
| .keys() | |
| .map(|&c| cx.is_active(c).map(|a| (a, c))), | |
| )?; | |
| let backtrack_critical_reason: ConflictReason = | |
| conflicting_activations[&backtrack_critical_id].clone(); | |
| if cx | |
| .parents | |
| .is_path_from_to(&parent.package_id(), &backtrack_critical_id) | |
| { | |
| // We are a descendant of the trigger of the problem. | |
| // The best generalization of this is to let things bubble up | |
| // and let `backtrack_critical_id` figure this out. | |
| return None; | |
| } | |
| // What parents does that critical activation have | |
| for (critical_parent, critical_parents_deps) in | |
| cx.parents.edges(&backtrack_critical_id).filter(|(p, _)| { | |
| // it will only help backjump further if it is older then the critical_age | |
| cx.is_active(**p).expect("parent not currently active!?") < backtrack_critical_age | |
| }) | |
| { | |
| for critical_parents_dep in critical_parents_deps.iter() { | |
| // We only want `first_version.is_some()` for direct dependencies of workspace | |
| // members which isn't the case here as this has a `parent` | |
| let first_version = None; | |
| // A dep is equivalent to one of the things it can resolve to. | |
| // Thus, if all the things it can resolve to have already ben determined | |
| // to be conflicting, then we can just say that we conflict with the parent. | |
| if let Some(others) = registry | |
| .query(critical_parents_dep, first_version) | |
| .expect("an already used dep now pending!?") | |
| .expect("an already used dep now error!?") | |
| .iter() | |
| .rev() // the last one to be tried is the least likely to be in the cache, so start with that. | |
| .map(|other| { | |
| past_conflicting_activations | |
| .find( | |
| dep, | |
| &|id| { | |
| if id == other.package_id() { | |
| // we are imagining that we used other instead | |
| Some(backtrack_critical_age) | |
| } else { | |
| cx.is_active(id) | |
| } | |
| }, | |
| Some(other.package_id()), | |
| // we only care about things that are newer then critical_age | |
| backtrack_critical_age, | |
| ) | |
| .map(|con| (other.package_id(), con)) | |
| }) | |
| .collect::<Option<Vec<(PackageId, &ConflictMap)>>>() | |
| { | |
| let mut con = conflicting_activations.clone(); | |
| // It is always valid to combine previously inserted conflicts. | |
| // A, B are both known bad states each that can never be activated. | |
| // A + B is redundant but can't be activated, as if | |
| // A + B is active then A is active and we know that is not ok. | |
| for (_, other) in &others { | |
| con.extend(other.iter().map(|(&id, re)| (id, re.clone()))); | |
| } | |
| // Now that we have this combined conflict, we can do a substitution: | |
| // A dep is equivalent to one of the things it can resolve to. | |
| // So we can remove all the things that it resolves to and replace with the parent. | |
| for (other_id, _) in &others { | |
| con.remove(other_id); | |
| } | |
| con.insert(*critical_parent, backtrack_critical_reason); | |
| if cfg!(debug_assertions) { | |
| // the entire point is to find an older conflict, so let's make sure we did | |
| let new_age = con | |
| .keys() | |
| .map(|&c| cx.is_active(c).expect("not currently active!?")) | |
| .max() | |
| .unwrap(); | |
| assert!( | |
| new_age < backtrack_critical_age, | |
| "new_age {} < backtrack_critical_age {}", | |
| new_age, | |
| backtrack_critical_age | |
| ); | |
| } | |
| past_conflicting_activations.insert(dep, &con); | |
| return Some(con); | |
| } | |
| } | |
| } | |
| None | |
| } | |
| /// Returns Some of the largest item in the iterator. | |
| /// Returns None if any of the items are None or the iterator is empty. | |
| fn shortcircuit_max<I: Ord>(iter: impl Iterator<Item = Option<I>>) -> Option<I> { | |
| let mut out = None; | |
| for i in iter { | |
| if i.is_none() { | |
| return None; | |
| } | |
| out = std::cmp::max(out, i); | |
| } | |
| out | |
| } | |
| /// Looks through the states in `backtrack_stack` for dependencies with | |
| /// remaining candidates. For each one, also checks if rolling back | |
| /// could change the outcome of the failed resolution that caused backtracking | |
| /// in the first place. Namely, if we've backtracked past the parent of the | |
| /// failed dep, or any of the packages flagged as giving us trouble in | |
| /// `conflicting_activations`. | |
| /// | |
| /// Read <https://github.com/rust-lang/cargo/pull/4834> | |
| /// For several more detailed explanations of the logic here. | |
| fn find_candidate( | |
| cx: &ResolverContext, | |
| backtrack_stack: &mut Vec<BacktrackFrame>, | |
| parent: &Summary, | |
| backtracked: bool, | |
| conflicting_activations: &ConflictMap, | |
| ) -> Option<(Summary, bool, BacktrackFrame)> { | |
| // When we're calling this method we know that `parent` failed to | |
| // activate. That means that some dependency failed to get resolved for | |
| // whatever reason. Normally, that means that all of those reasons | |
| // (plus maybe some extras) are listed in `conflicting_activations`. | |
| // | |
| // The abnormal situations are things that do not put all of the reasons in `conflicting_activations`: | |
| // If we backtracked we do not know how our `conflicting_activations` related to | |
| // the cause of that backtrack, so we do not update it. | |
| let age = if !backtracked { | |
| // we don't have abnormal situations. So we can ask `cx` for how far back we need to go. | |
| // If the `conflicting_activations` does not apply to `cx`, | |
| // we will just fall back to laboriously trying all possibilities witch | |
| // will give us the correct answer. | |
| cx.is_conflicting(Some(parent.package_id()), conflicting_activations) | |
| } else { | |
| None | |
| }; | |
| let mut new_frame = None; | |
| if let Some(age) = age { | |
| while let Some(frame) = backtrack_stack.pop() { | |
| // If all members of `conflicting_activations` are still | |
| // active in this back up we know that we're guaranteed to not actually | |
| // make any progress. As a result if we hit this condition we can | |
| // completely skip this backtrack frame and move on to the next. | |
| // Above we use `cx` to determine if this is going to be conflicting. | |
| // But lets just double check if the `pop`ed frame agrees. | |
| let frame_too_new = frame.context.age >= age; | |
| debug_assert!( | |
| frame | |
| .context | |
| .is_conflicting(Some(parent.package_id()), conflicting_activations) | |
| == frame_too_new.then_some(age) | |
| ); | |
| if !frame_too_new { | |
| new_frame = Some(frame); | |
| break; | |
| } | |
| trace!( | |
| "{} = \"{}\" skip as not solving {}: {:?}", | |
| frame.dep.package_name(), | |
| frame.dep.version_req(), | |
| parent.package_id(), | |
| conflicting_activations | |
| ); | |
| } | |
| } else { | |
| // If we're here then we are in abnormal situations and need to just go one frame at a time. | |
| new_frame = backtrack_stack.pop(); | |
| } | |
| new_frame.map(|mut frame| { | |
| let (candidate, has_another) = frame | |
| .remaining_candidates | |
| .next(&mut frame.conflicting_activations, &frame.context) | |
| .expect("why did we save a frame that has no next?"); | |
| (candidate, has_another, frame) | |
| }) | |
| } | |
| fn check_cycles(resolve: &Resolve) -> CargoResult<()> { | |
| // Perform a simple cycle check by visiting all nodes. | |
| // We visit each node at most once and we keep | |
| // track of the path through the graph as we walk it. If we walk onto the | |
| // same node twice that's a cycle. | |
| let mut checked = HashSet::with_capacity_and_hasher(resolve.len(), FxBuildHasher::default()); | |
| let mut path = Vec::with_capacity(4); | |
| let mut visited = HashSet::with_capacity_and_hasher(4, FxBuildHasher::default()); | |
| for pkg in resolve.iter() { | |
| if !checked.contains(&pkg) { | |
| visit(&resolve, pkg, &mut visited, &mut path, &mut checked)? | |
| } | |
| } | |
| return Ok(()); | |
| fn visit( | |
| resolve: &Resolve, | |
| id: PackageId, | |
| visited: &mut HashSet<PackageId>, | |
| path: &mut Vec<PackageId>, | |
| checked: &mut HashSet<PackageId>, | |
| ) -> CargoResult<()> { | |
| if !visited.insert(id) { | |
| // We found a cycle and need to construct an error. Performance is no longer top priority. | |
| let iter = path.iter().rev().scan(id, |child, parent| { | |
| let dep = resolve.transitive_deps_not_replaced(*parent).find_map( | |
| |(dep_id, transitive_dep)| { | |
| (*child == dep_id || Some(*child) == resolve.replacement(dep_id)) | |
| .then_some(transitive_dep) | |
| }, | |
| ); | |
| *child = *parent; | |
| Some((parent, dep)) | |
| }); | |
| let iter = std::iter::once((&id, None)).chain(iter); | |
| let describe_path = errors::describe_path(iter); | |
| anyhow::bail!( | |
| "cyclic package dependency: package `{id}` depends on itself. Cycle:\n{describe_path}" | |
| ); | |
| } | |
| if checked.insert(id) { | |
| path.push(id); | |
| for (dep_id, _transitive_dep) in resolve.transitive_deps_not_replaced(id) { | |
| visit(resolve, dep_id, visited, path, checked)?; | |
| if let Some(replace_id) = resolve.replacement(dep_id) { | |
| visit(resolve, replace_id, visited, path, checked)?; | |
| } | |
| } | |
| path.pop(); | |
| } | |
| visited.remove(&id); | |
| Ok(()) | |
| } | |
| } | |
| /// Checks that packages are unique when written to lock file. | |
| /// | |
| /// When writing package ID's to lock file, we apply lossy encoding. In | |
| /// particular, we don't store paths of path dependencies. That means that | |
| /// *different* packages may collide in the lock file, hence this check. | |
| fn check_duplicate_pkgs_in_lockfile(resolve: &Resolve) -> CargoResult<()> { | |
| let mut unique_pkg_ids = HashMap::default(); | |
| let state = encode::EncodeState::new(resolve); | |
| for pkg_id in resolve.iter() { | |
| let encodable_pkd_id = encode::encodable_package_id(pkg_id, &state, resolve.version()); | |
| if let Some(prev_pkg_id) = unique_pkg_ids.insert(encodable_pkd_id, pkg_id) { | |
| anyhow::bail!( | |
| "package collision in the lockfile: packages {} and {} are different, \ | |
| but only one can be written to lockfile unambiguously", | |
| prev_pkg_id, | |
| pkg_id | |
| ) | |
| } | |
| } | |
| Ok(()) | |
| } |