use std::collections::BTreeSet as Set;
use std::error::Error;
use std::fmt::{Debug, Display};
use crate::internal::{Id, Incompatibility, State};
use crate::{Map, Package, PubGrubError, Term, VersionSet};
use log::{debug, info};
#[derive(Debug, Default, Clone)]
pub struct PackageResolutionStatistics {
unit_propagation_affected: u32,
unit_propagation_culprit: u32,
dependencies_affected: u32,
dependencies_culprit: u32,
}
impl PackageResolutionStatistics {
pub fn conflict_count(&self) -> u32 {
self.unit_propagation_affected
+ self.unit_propagation_culprit
+ self.dependencies_affected
+ self.dependencies_culprit
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SelectedDependencies<P: Package, V>(Map<P, V>);
impl<P: Package, V> SelectedDependencies<P, V> {
pub fn iter(&self) -> impl Iterator<Item = (&P, &V)> {
self.0.iter()
}
pub fn get(&self, package: &P) -> Option<&V> {
self.0.get(package)
}
}
impl<P: Package, V> FromIterator<(P, V)> for SelectedDependencies<P, V> {
fn from_iter<I: IntoIterator<Item = (P, V)>>(iter: I) -> Self {
Self(Map::from_iter(iter))
}
}
impl<P: Package, V> IntoIterator for SelectedDependencies<P, V> {
type Item = (P, V);
type IntoIter = <Map<P, V> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cold]
pub fn resolve<DP: DependencyProvider>(
dependency_provider: &DP,
package: DP::P,
version: impl Into<DP::V>,
) -> Result<SelectedDependencies<DP::P, DP::V>, PubGrubError<DP>> {
let mut state: State<DP> = State::init(package.clone(), version.into());
let mut conflict_tracker: Map<Id<DP::P>, PackageResolutionStatistics> = Map::default();
let mut added_dependencies: Map<Id<DP::P>, Set<DP::V>> = Map::default();
let mut next = state.root_package;
loop {
dependency_provider
.should_cancel()
.map_err(|err| PubGrubError::ErrorInShouldCancel(err))?;
info!(
"unit_propagation: {:?} = '{}'",
&next, state.package_store[next]
);
let satisfier_causes = state.unit_propagation(next)?;
for (affected, incompat) in satisfier_causes {
conflict_tracker
.entry(affected)
.or_default()
.unit_propagation_affected += 1;
for (conflict_package, _) in state.incompatibility_store[incompat].iter() {
if conflict_package == affected {
continue;
}
conflict_tracker
.entry(conflict_package)
.or_default()
.unit_propagation_culprit += 1;
}
}
debug!(
"Partial solution after unit propagation: {}",
state.partial_solution.display(&state.package_store)
);
let Some((highest_priority_pkg, term_intersection)) =
state.partial_solution.pick_highest_priority_pkg(|p, r| {
dependency_provider.prioritize(
&state.package_store[p],
r,
conflict_tracker.entry(p).or_default(),
)
})
else {
return Ok(SelectedDependencies(
state
.partial_solution
.extract_solution()
.map(|(p, v)| (state.package_store[p].clone(), v))
.collect(),
));
};
next = highest_priority_pkg;
let decision = dependency_provider
.choose_version(&state.package_store[next], term_intersection)
.map_err(|err| PubGrubError::ErrorChoosingVersion {
package: state.package_store[next].clone(),
source: err,
})?;
info!(
"DP chose: {:?} = '{}' @ {:?}",
&next, state.package_store[next], decision
);
let v = match decision {
None => {
let inc =
Incompatibility::no_versions(next, Term::Positive(term_intersection.clone()));
state.add_incompatibility(inc);
continue;
}
Some(x) => x,
};
if !term_intersection.contains(&v) {
panic!(
"`choose_version` picked an incompatible version for package {}, {} is not in {}",
state.package_store[next], v, term_intersection
);
}
let is_new_dependency = added_dependencies
.entry(next)
.or_default()
.insert(v.clone());
if is_new_dependency {
let p = next;
let dependencies = dependency_provider
.get_dependencies(&state.package_store[p], &v)
.map_err(|err| PubGrubError::ErrorRetrievingDependencies {
package: state.package_store[p].clone(),
version: v.clone(),
source: err,
})?;
let dependencies = match dependencies {
Dependencies::Unavailable(reason) => {
state.add_incompatibility(Incompatibility::custom_version(
p,
v.clone(),
reason,
));
continue;
}
Dependencies::Available(x) => x,
};
if let Some(conflict) =
state.add_package_version_dependencies(p, v.clone(), dependencies)
{
conflict_tracker.entry(p).or_default().dependencies_affected += 1;
for (incompat_package, _) in state.incompatibility_store[conflict].iter() {
if incompat_package == p {
continue;
}
conflict_tracker
.entry(incompat_package)
.or_default()
.dependencies_culprit += 1;
}
}
} else {
info!(
"add_decision (not first time): {:?} = '{}' @ {}",
&next, state.package_store[next], v
);
state.partial_solution.add_decision(next, v);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DependencyConstraints<P, VS>(Vec<(P, VS)>);
#[cfg(feature = "serde")]
impl<P: Package + serde::Serialize, VS: serde::Serialize> serde::Serialize
for DependencyConstraints<P, VS>
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Map::from_iter(self.0.iter().map(|(p, v)| (p, v))).serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, P: Package + serde::Deserialize<'de>, VS: serde::Deserialize<'de>> serde::Deserialize<'de>
for DependencyConstraints<P, VS>
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self::from_iter(Map::deserialize(deserializer)?))
}
}
impl<P, VS> DependencyConstraints<P, VS> {
pub fn iter(&self) -> impl Iterator<Item = &(P, VS)> {
self.0.iter()
}
}
impl<P, VS> Default for DependencyConstraints<P, VS> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<P, VS> FromIterator<(P, VS)> for DependencyConstraints<P, VS> {
fn from_iter<T: IntoIterator<Item = (P, VS)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<P, VS> IntoIterator for DependencyConstraints<P, VS> {
type Item = (P, VS);
type IntoIter = <Vec<(P, VS)> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[derive(Clone)]
pub enum Dependencies<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
Unavailable(M),
Available(DependencyConstraints<P, VS>),
}
pub trait DependencyProvider {
type P: Package;
type V: Debug + Display + Clone + Ord;
type VS: VersionSet<V = Self::V>;
type Priority: Ord + Clone;
type M: Eq + Clone + Debug + Display;
type Err: Error + 'static;
fn prioritize(
&self,
package: &Self::P,
range: &Self::VS,
package_conflicts_counts: &PackageResolutionStatistics,
) -> Self::Priority;
fn choose_version(
&self,
package: &Self::P,
range: &Self::VS,
) -> Result<Option<Self::V>, Self::Err>;
#[allow(clippy::type_complexity)]
fn get_dependencies(
&self,
package: &Self::P,
version: &Self::V,
) -> Result<Dependencies<Self::P, Self::VS, Self::M>, Self::Err>;
fn should_cancel(&self) -> Result<(), Self::Err> {
Ok(())
}
}