use std::collections::{BTreeMap, HashMap, HashSet};
use std::mem;
use std::rc::Rc;
use std::time::{Duration, Instant};
use log::{debug, trace};
use crate::core::PackageIdSpec;
use crate::core::{Dependency, PackageId, Registry, Summary};
use crate::util::config::Config;
use crate::util::errors::CargoResult;
use crate::util::profile;
use self::context::Context;
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::encode::Metadata;
pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve};
pub use self::errors::{ActivateError, ActivateResult, ResolveError};
pub use self::features::HasDevUnits;
pub use self::resolve::{Resolve, ResolveVersion};
pub use self::types::ResolveOpts;
mod conflict_cache;
mod context;
mod dep_cache;
mod encode;
mod errors;
pub mod features;
mod resolve;
mod types;
pub fn resolve(
summaries: &[(Summary, ResolveOpts)],
replacements: &[(PackageIdSpec, Dependency)],
registry: &mut dyn Registry,
try_to_use: &HashSet<PackageId>,
config: Option<&Config>,
check_public_visible_dependencies: bool,
) -> CargoResult<Resolve> {
let cx = Context::new(check_public_visible_dependencies);
let _p = profile::start("resolving");
let minimal_versions = match config {
Some(config) => config.cli_unstable().minimal_versions,
None => false,
};
let mut registry = RegistryQueryer::new(registry, replacements, try_to_use, minimal_versions);
let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;
let mut cksums = HashMap::new();
for (summary, _) in cx.activations.values() {
let cksum = summary.checksum().map(|s| s.to_string());
cksums.insert(summary.package_id(), cksum);
}
let graph = cx.graph();
let replacements = cx.resolve_replacements(®istry);
let features = cx
.resolve_features
.iter()
.map(|(k, v)| (*k, v.iter().cloned().collect()))
.collect();
let summaries = cx
.activations
.into_iter()
.map(|(_key, (summary, _age))| (summary.package_id(), summary))
.collect();
let resolve = Resolve::new(
graph,
replacements,
features,
cksums,
BTreeMap::new(),
Vec::new(),
ResolveVersion::default_for_new_lockfiles(),
summaries,
);
check_cycles(&resolve)?;
check_duplicate_pkgs_in_lockfile(&resolve)?;
trace!("resolved: {:?}", resolve);
Ok(resolve)
}
fn activate_deps_loop(
mut cx: Context,
registry: &mut RegistryQueryer<'_>,
summaries: &[(Summary, ResolveOpts)],
config: Option<&Config>,
) -> CargoResult<Context> {
let mut backtrack_stack = Vec::new();
let mut remaining_deps = RemainingDeps::new();
let mut past_conflicting_activations = conflict_cache::ConflictCache::new();
for &(ref summary, ref opts) in summaries {
debug!("initial activation: {}", summary.package_id());
let res = activate(&mut cx, registry, None, summary.clone(), opts.clone());
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();
while let Some((just_here_for_the_error_messages, frame)) =
remaining_deps.pop_most_constrained()
{
let (mut parent, (mut dep, candidates, mut features)) = frame;
printed.shell_status(config)?;
trace!(
"{}[{}]>{} {} candidates",
parent.name(),
cx.age,
dep.package_name(),
candidates.len()
);
let just_here_for_the_error_messages = just_here_for_the_error_messages
&& past_conflicting_activations
.conflicting(&cx, &dep)
.is_some();
let mut remaining_candidates = RemainingCandidates::new(&candidates);
let mut conflicting_activations = ConflictMap::new();
let mut backtracked = false;
loop {
let next = remaining_candidates.next(
&mut conflicting_activations,
&cx,
&dep,
parent.package_id(),
);
let (candidate, has_another) = next.ok_or(()).or_else(|_| {
trace!(
"{}[{}]>{} -- no candidates",
parent.name(),
cx.age,
dep.package_name()
);
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(
&cx,
registry,
&mut past_conflicting_activations,
&parent,
&dep,
&conflicting_activations,
) {
generalize_conflicting_activations = Some(c);
}
}
match find_candidate(
&cx,
&mut backtrack_stack,
&parent,
backtracked,
generalize_conflicting_activations
.as_ref()
.unwrap_or(&conflicting_activations),
) {
Some((candidate, has_another, frame)) => {
cx = 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(
&cx,
registry.registry,
&parent,
&dep,
&conflicting_activations,
&candidates,
config,
))
}
}
})?;
if just_here_for_the_error_messages && !backtracked && has_another {
continue;
}
let backtrack = if has_another {
Some(BacktrackFrame {
context: Context::clone(&cx),
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 {
features: Rc::clone(&features),
all_features: false,
uses_default_features: dep.uses_default_features(),
},
};
trace!(
"{}[{}]>{} trying {}",
parent.name(),
cx.age,
dep.package_name(),
candidate.version()
);
let res = activate(&mut cx, registry, Some((&parent, &dep)), candidate, opts);
let successfully_activated = match res {
Ok(Some((mut frame, dur))) => {
printed.elapsed(dur);
let mut has_past_conflicting_dep = just_here_for_the_error_messages;
if !has_past_conflicting_dep {
if let Some(conflicting) = frame
.remaining_siblings
.clone()
.filter_map(|(ref new_dep, _, _)| {
past_conflicting_activations.conflicting(&cx, new_dep)
})
.next()
{
conflicting_activations.extend(
conflicting
.iter()
.filter(|&(p, _)| p != &pid)
.map(|(&p, r)| (p, r.clone())),
);
has_past_conflicting_dep = true;
}
}
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()
.filter(|&(_, ref other_dep)| {
known_related_bad_deps.contains(other_dep)
})
.filter_map(|(other_parent, other_dep)| {
past_conflicting_activations
.find_conflicting(&cx, &other_dep, Some(pid))
.map(|con| (other_parent, con))
})
.next()
{
let rel = conflict.get(&pid).unwrap().clone();
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;
}
}
}
let activate_for_error_message = has_past_conflicting_dep && !has_another && {
just_here_for_the_error_messages || {
find_candidate(
&cx,
&mut backtrack_stack.clone(),
&parent,
backtracked,
&conflicting_activations,
)
.is_none()
}
};
if activate_for_error_message {
backtrack_stack.clear();
}
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(),
cx.age,
dep.package_name(),
pid.version()
);
false
}
}
Ok(None) => true,
Err(ActivateError::Fatal(e)) => return Err(e),
Err(ActivateError::Conflict(id, reason)) => {
conflicting_activations.insert(id, reason);
false
}
};
if successfully_activated {
backtrack_stack.extend(backtrack);
break;
}
if let Some(b) = backtrack {
cx = b.context;
}
}
}
Ok(cx)
}
fn activate(
cx: &mut Context,
registry: &mut RegistryQueryer<'_>,
parent: Option<(&Summary, &Dependency)>,
candidate: Summary,
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();
cx.parents
.link(candidate_pid, parent_pid)
.insert(dep.clone());
if let Some(public_dependency) = cx.public_dependency.as_mut() {
public_dependency.add_edge(
candidate_pid,
parent_pid,
dep.is_public(),
cx.age,
&cx.parents,
);
}
}
let activated = cx.flag_activated(&candidate, &opts, parent)?;
let candidate = match registry.replacement_summary(candidate_pid) {
Some(replace) => {
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)?;
if !used_features.is_empty() {
Rc::make_mut(
cx.resolve_features
.entry(candidate.package_id())
.or_insert_with(Rc::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: Context,
remaining_deps: RemainingDeps,
remaining_candidates: RemainingCandidates,
parent: Summary,
dep: Dependency,
features: FeaturesSet,
conflicting_activations: ConflictMap,
}
#[derive(Clone)]
struct RemainingCandidates {
remaining: RcVecIter<Summary>,
has_another: Option<Summary>,
}
impl RemainingCandidates {
fn new(candidates: &Rc<Vec<Summary>>) -> RemainingCandidates {
RemainingCandidates {
remaining: RcVecIter::new(Rc::clone(candidates)),
has_another: None,
}
}
fn next(
&mut self,
conflicting_prev_active: &mut ConflictMap,
cx: &Context,
dep: &Dependency,
parent: PackageId,
) -> Option<(Summary, bool)> {
for b in self.remaining.by_ref() {
let b_id = b.package_id();
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;
}
}
}
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;
}
}
if let Some(public_dependency) = cx.public_dependency.as_ref() {
if let Err(((c1, c2), c3)) =
public_dependency.can_add_edge(b_id, parent, dep.is_public(), &cx.parents)
{
conflicting_prev_active.insert(c1.0, c1.1);
conflicting_prev_active.insert(c2.0, c2.1);
if let Some(c3) = c3 {
conflicting_prev_active.insert(c3.0, c3.1);
}
continue;
}
}
if let Some(r) = mem::replace(&mut self.has_another, Some(b)) {
return Some((r, true));
}
}
self.has_another.take().map(|r| (r, false))
}
}
fn generalize_conflicting(
cx: &Context,
registry: &mut RegistryQueryer<'_>,
past_conflicting_activations: &mut conflict_cache::ConflictCache,
parent: &Summary,
dep: &Dependency,
conflicting_activations: &ConflictMap,
) -> Option<ConflictMap> {
if conflicting_activations.is_empty() {
return None;
}
let (backtrack_critical_age, backtrack_critical_id) = conflicting_activations
.keys()
.map(|&c| (cx.is_active(c).expect("not currently active!?"), c))
.max()
.unwrap();
let backtrack_critical_reason: ConflictReason =
conflicting_activations[&backtrack_critical_id].clone();
if backtrack_critical_reason.is_public_dependency() {
return None;
}
if cx
.parents
.is_path_from_to(&parent.package_id(), &backtrack_critical_id)
{
return None;
}
for (critical_parent, critical_parents_deps) in
cx.parents.edges(&backtrack_critical_id).filter(|(p, _)| {
cx.is_active(*p).expect("parent not currently active!?") < backtrack_critical_age
})
{
for critical_parents_dep in critical_parents_deps.iter() {
if let Some(others) = registry
.query(critical_parents_dep)
.expect("an already used dep now error!?")
.iter()
.rev() .map(|other| {
past_conflicting_activations
.find(
dep,
&|id| {
if id == other.package_id() {
Some(backtrack_critical_age)
} else {
cx.is_active(id)
}
},
Some(other.package_id()),
backtrack_critical_age,
)
.map(|con| (other.package_id(), con))
})
.collect::<Option<Vec<(PackageId, &ConflictMap)>>>()
{
let mut con = conflicting_activations.clone();
for (_, other) in &others {
con.extend(other.iter().map(|(&id, re)| (id, re.clone())));
}
for (other_id, _) in &others {
con.remove(other_id);
}
con.insert(*critical_parent, backtrack_critical_reason);
if cfg!(debug_assertions) {
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
}
fn find_candidate(
cx: &Context,
backtrack_stack: &mut Vec<BacktrackFrame>,
parent: &Summary,
backtracked: bool,
conflicting_activations: &ConflictMap,
) -> Option<(Summary, bool, BacktrackFrame)> {
let age = if !backtracked {
let a = cx.is_conflicting(Some(parent.package_id()), conflicting_activations);
debug_assert!(a.is_some());
a
} else {
None
};
while let Some(mut frame) = backtrack_stack.pop() {
let next = frame.remaining_candidates.next(
&mut frame.conflicting_activations,
&frame.context,
&frame.dep,
frame.parent.package_id(),
);
let (candidate, has_another) = match next {
Some(pair) => pair,
None => continue,
};
if let Some(age) = age {
if frame.context.age >= age {
trace!(
"{} = \"{}\" skip as not solving {}: {:?}",
frame.dep.package_name(),
frame.dep.version_req(),
parent.package_id(),
conflicting_activations
);
debug_assert!(
frame
.context
.is_conflicting(Some(parent.package_id()), conflicting_activations)
== Some(age)
);
continue;
} else {
debug_assert!(frame
.context
.is_conflicting(Some(parent.package_id()), conflicting_activations)
.is_none());
}
}
return Some((candidate, has_another, frame));
}
None
}
fn check_cycles(resolve: &Resolve) -> CargoResult<()> {
let mut all_packages: Vec<_> = resolve.iter().collect();
all_packages.sort_unstable();
let mut checked = HashSet::new();
let mut path = Vec::new();
let mut visited = HashSet::new();
for pkg in all_packages {
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<()> {
path.push(id);
if !visited.insert(id) {
anyhow::bail!(
"cyclic package dependency: package `{}` depends on itself. Cycle:\n{}",
id,
errors::describe_path(&path.iter().rev().collect::<Vec<_>>()),
);
}
if checked.insert(id) {
let mut empty_set = HashSet::new();
let mut empty_vec = Vec::new();
for (dep, listings) in resolve.deps_not_replaced(id) {
let is_transitive = listings.iter().any(|d| d.is_transitive());
let (visited, path) = if is_transitive {
(&mut *visited, &mut *path)
} else {
(&mut empty_set, &mut empty_vec)
};
visit(resolve, dep, visited, path, checked)?;
if let Some(id) = resolve.replacement(dep) {
visit(resolve, id, visited, path, checked)?;
}
}
}
path.pop();
visited.remove(&id);
Ok(())
}
}
fn check_duplicate_pkgs_in_lockfile(resolve: &Resolve) -> CargoResult<()> {
let mut unique_pkg_ids = HashMap::new();
let state = encode::EncodeState::new(resolve);
for pkg_id in resolve.iter() {
let encodable_pkd_id = encode::encodable_package_id(pkg_id, &state);
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(())
}