use std::borrow::Borrow;
use std::collections::{BTreeMap, BTreeSet as Set};
use std::error::Error;
use crate::error::PubGrubError;
use crate::internal::core::State;
use crate::internal::incompatibility::Incompatibility;
use crate::package::Package;
use crate::range::Range;
use crate::type_aliases::{Map, SelectedDependencies};
use crate::version::Version;
pub fn resolve<P: Package, V: Version>(
dependency_provider: &impl DependencyProvider<P, V>,
package: P,
version: impl Into<V>,
) -> Result<SelectedDependencies<P, V>, PubGrubError<P, V>> {
let mut state = State::init(package.clone(), version.into());
let mut added_dependencies: Map<P, Set<V>> = Map::default();
let mut next = package;
loop {
dependency_provider
.should_cancel()
.map_err(|err| PubGrubError::ErrorInShouldCancel(err))?;
state.unit_propagation(next)?;
let potential_packages = state.partial_solution.potential_packages();
if potential_packages.is_none() {
drop(potential_packages);
return state.partial_solution.extract_solution().ok_or_else(|| {
PubGrubError::Failure(
"How did we end up with no package to choose but no solution?".into(),
)
});
}
let decision = dependency_provider
.choose_package_version(potential_packages.unwrap())
.map_err(PubGrubError::ErrorChoosingPackageVersion)?;
next = decision.0.clone();
let term_intersection = state
.partial_solution
.term_intersection_for_package(&next)
.expect("a package was chosen but we don't have a term.");
let v = match decision.1 {
None => {
let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone());
state.add_incompatibility(inc);
continue;
}
Some(x) => x,
};
if !term_intersection.contains(&v) {
return Err(PubGrubError::ErrorChoosingPackageVersion(
"choose_package_version picked an incompatible version".into(),
));
}
if added_dependencies
.entry(next.clone())
.or_default()
.insert(v.clone())
{
let p = &next;
let dependencies =
match dependency_provider
.get_dependencies(&p, &v)
.map_err(|err| PubGrubError::ErrorRetrievingDependencies {
package: p.clone(),
version: v.clone(),
source: err,
})? {
Dependencies::Unknown => {
state.add_incompatibility(Incompatibility::unavailable_dependencies(
p.clone(),
v.clone(),
));
continue;
}
Dependencies::Known(x) => {
if x.contains_key(&p) {
return Err(PubGrubError::SelfDependency {
package: p.clone(),
version: v.clone(),
});
}
if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&Range::none()) {
return Err(PubGrubError::DependencyOnTheEmptySet {
package: p.clone(),
version: v.clone(),
dependent: dependent.clone(),
});
}
x
}
};
let dep_incompats =
state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &dependencies);
if state.incompatibility_store[dep_incompats.clone()]
.iter()
.any(|incompat| state.is_terminal(incompat))
{
return Err(PubGrubError::Failure(
"Root package depends on itself at a different version?".into(),
));
}
state.partial_solution.add_version(
p.clone(),
v,
dep_incompats,
&state.incompatibility_store,
);
} else {
state.partial_solution.add_decision(next.clone(), v);
}
}
}
#[derive(Clone)]
pub enum Dependencies<P: Package, V: Version> {
Unknown,
Known(DependencyConstraints<P, V>),
}
pub type DependencyConstraints<P, V> = Map<P, Range<V>>;
pub trait DependencyProvider<P: Package, V: Version> {
fn choose_package_version<T: Borrow<P>, U: Borrow<Range<V>>>(
&self,
potential_packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error>>;
fn get_dependencies(
&self,
package: &P,
version: &V,
) -> Result<Dependencies<P, V>, Box<dyn Error>>;
fn should_cancel(&self) -> Result<(), Box<dyn Error>> {
Ok(())
}
}
pub fn choose_package_with_fewest_versions<P: Package, V: Version, T, U, I, F>(
list_available_versions: F,
potential_packages: impl Iterator<Item = (T, U)>,
) -> (T, Option<V>)
where
T: Borrow<P>,
U: Borrow<Range<V>>,
I: Iterator<Item = V>,
F: Fn(&P) -> I,
{
let count_valid = |(p, range): &(T, U)| {
list_available_versions(p.borrow())
.filter(|v| range.borrow().contains(v.borrow()))
.count()
};
let (pkg, range) = potential_packages
.min_by_key(count_valid)
.expect("potential_packages gave us an empty iterator");
let version =
list_available_versions(pkg.borrow()).find(|v| range.borrow().contains(v.borrow()));
(pkg, version)
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct OfflineDependencyProvider<P: Package, V: Version> {
dependencies: Map<P, BTreeMap<V, DependencyConstraints<P, V>>>,
}
impl<P: Package, V: Version> OfflineDependencyProvider<P, V> {
pub fn new() -> Self {
Self {
dependencies: Map::default(),
}
}
pub fn add_dependencies<I: IntoIterator<Item = (P, Range<V>)>>(
&mut self,
package: P,
version: impl Into<V>,
dependencies: I,
) {
let package_deps = dependencies.into_iter().collect();
let v = version.into();
*self
.dependencies
.entry(package)
.or_default()
.entry(v)
.or_default() = package_deps;
}
pub fn packages(&self) -> impl Iterator<Item = &P> {
self.dependencies.keys()
}
pub fn versions(&self, package: &P) -> Option<impl Iterator<Item = &V>> {
self.dependencies.get(package).map(|k| k.keys())
}
fn dependencies(&self, package: &P, version: &V) -> Option<DependencyConstraints<P, V>> {
self.dependencies.get(package)?.get(version).cloned()
}
}
impl<P: Package, V: Version> DependencyProvider<P, V> for OfflineDependencyProvider<P, V> {
fn choose_package_version<T: Borrow<P>, U: Borrow<Range<V>>>(
&self,
potential_packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error>> {
Ok(choose_package_with_fewest_versions(
|p| {
self.dependencies
.get(p)
.into_iter()
.flat_map(|k| k.keys())
.rev()
.cloned()
},
potential_packages,
))
}
fn get_dependencies(
&self,
package: &P,
version: &V,
) -> Result<Dependencies<P, V>, Box<dyn Error>> {
Ok(match self.dependencies(package, version) {
None => Dependencies::Unknown,
Some(dependencies) => Dependencies::Known(dependencies),
})
}
}