Skip to content

Commit 3d8fd9d

Browse files
authored
feat!: let DependencyProvider prioritize dependencies (#50)
1 parent 27c1eb2 commit 3d8fd9d

10 files changed

+227
-167
lines changed

benches/large_case.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::time::Duration;
44
extern crate criterion;
55
use self::criterion::*;
66

7-
use pubgrub::solver::{resolve, DependencyProvider, OfflineDependencyProvider};
7+
use pubgrub::solver::{resolve, OfflineDependencyProvider};
88
use pubgrub::version::NumberVersion;
99

1010
fn bench_nested(c: &mut Criterion) {
@@ -20,10 +20,9 @@ fn bench_nested(c: &mut Criterion) {
2020
let s = std::fs::read_to_string(&case).unwrap();
2121
let dependency_provider: OfflineDependencyProvider<u16, NumberVersion> =
2222
ron::de::from_str(&s).unwrap();
23-
let all_versions = dependency_provider.list_available_versions(&0).unwrap();
2423

2524
b.iter(|| {
26-
for &n in &all_versions {
25+
for &n in dependency_provider.versions(&0).unwrap() {
2726
let _ = resolve(&dependency_provider, 0, n);
2827
}
2928
});

examples/caching_dependency_provider.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@
22

33
use std::cell::RefCell;
44
use std::error::Error;
5-
use std::hash::Hash;
65

76
use pubgrub::package::Package;
7+
use pubgrub::range::Range;
88
use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider};
99
use pubgrub::version::{NumberVersion, Version};
1010

1111
// An example implementing caching dependency provider that will
1212
// store queried dependencies in memory and check them before querying more from remote.
13-
struct CachingDependencyProvider<P: Package, V: Version + Hash, DP: DependencyProvider<P, V>> {
13+
struct CachingDependencyProvider<P: Package, V: Version, DP: DependencyProvider<P, V>> {
1414
remote_dependencies: DP,
1515
cached_dependencies: RefCell<OfflineDependencyProvider<P, V>>,
1616
}
1717

18-
impl<P: Package, V: Version + Hash, DP: DependencyProvider<P, V>>
19-
CachingDependencyProvider<P, V, DP>
20-
{
18+
impl<P: Package, V: Version, DP: DependencyProvider<P, V>> CachingDependencyProvider<P, V, DP> {
2119
pub fn new(remote_dependencies_provider: DP) -> Self {
2220
CachingDependencyProvider {
2321
remote_dependencies: remote_dependencies_provider,
@@ -26,12 +24,14 @@ impl<P: Package, V: Version + Hash, DP: DependencyProvider<P, V>>
2624
}
2725
}
2826

29-
impl<P: Package, V: Version + Hash, DP: DependencyProvider<P, V>> DependencyProvider<P, V>
27+
impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P, V>
3028
for CachingDependencyProvider<P, V, DP>
3129
{
32-
// Lists only from remote for simplicity
33-
fn list_available_versions(&self, package: &P) -> Result<Vec<V>, Box<dyn Error>> {
34-
self.remote_dependencies.list_available_versions(package)
30+
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<Range<V>>>(
31+
&self,
32+
packages: impl Iterator<Item = (T, U)>,
33+
) -> Result<(T, Option<V>), Box<dyn Error>> {
34+
self.remote_dependencies.choose_package_version(packages)
3535
}
3636

3737
// Caches dependencies if they were already queried

src/error.rs

+7-13
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,6 @@ pub enum PubGrubError<P: Package, V: Version> {
1515
#[error("No solution")]
1616
NoSolution(DerivationTree<P, V>),
1717

18-
/// Error arising when the implementer of
19-
/// [DependencyProvider](crate::solver::DependencyProvider)
20-
/// returned an error in the method
21-
/// [list_available_versions](crate::solver::DependencyProvider::list_available_versions).
22-
#[error("Retrieving available versions of package {package} failed")]
23-
ErrorRetrievingVersions {
24-
/// Package for which we want the list of versions.
25-
package: P,
26-
/// Error raised by the implementer of
27-
/// [DependencyProvider](crate::solver::DependencyProvider).
28-
source: Box<dyn std::error::Error>,
29-
},
30-
3118
/// Error arising when the implementer of
3219
/// [DependencyProvider](crate::solver::DependencyProvider)
3320
/// returned an error in the method
@@ -71,6 +58,13 @@ pub enum PubGrubError<P: Package, V: Version> {
7158
version: V,
7259
},
7360

61+
/// Error arising when the implementer of
62+
/// [DependencyProvider](crate::solver::DependencyProvider)
63+
/// returned an error in the method
64+
/// [choose_package_version](crate::solver::DependencyProvider::choose_package_version).
65+
#[error("Decision making failed")]
66+
ErrorChoosingPackageVersion(Box<dyn std::error::Error>),
67+
7468
/// Error arising when the implementer of [DependencyProvider](crate::solver::DependencyProvider)
7569
/// returned an error in the method [should_cancel](crate::solver::DependencyProvider::should_cancel).
7670
#[error("We should cancel")]

src/internal/memory.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use crate::internal::assignment::Assignment::{self, Decision, Derivation};
77
use crate::package::Package;
8+
use crate::range::Range;
89
use crate::term::Term;
910
use crate::type_aliases::{Map, SelectedDependencies};
1011
use crate::version::Version;
@@ -102,7 +103,7 @@ impl<P: Package, V: Version> Memory<P, V> {
102103
/// selected version (no "decision")
103104
/// and if it contains at least one positive derivation term
104105
/// in the partial solution.
105-
pub fn potential_packages(&mut self) -> impl Iterator<Item = (&P, &Term<V>)> {
106+
pub fn potential_packages(&mut self) -> impl Iterator<Item = (&P, &Range<V>)> {
106107
self.assignments
107108
.iter_mut()
108109
.filter_map(|(p, pa)| pa.potential_package_filter(p))
@@ -160,7 +161,7 @@ impl<V: Version> PackageAssignments<V> {
160161
fn potential_package_filter<'a, P: Package>(
161162
&'a mut self,
162163
package: &'a P,
163-
) -> Option<(&'a P, &'a Term<V>)> {
164+
) -> Option<(&'a P, &'a Range<V>)> {
164165
match self {
165166
PackageAssignments::Decision(_) => None,
166167
PackageAssignments::Derivations {
@@ -169,7 +170,7 @@ impl<V: Version> PackageAssignments<V> {
169170
} => {
170171
if intersected.is_positive() || not_intersected_yet.iter().any(|t| t.is_positive())
171172
{
172-
Some((package, self.assignment_intersection()))
173+
Some((package, self.assignment_intersection().unwrap_positive()))
173174
} else {
174175
None
175176
}

src/internal/partial_solution.rs

+16-51
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,14 @@
33
//! The partial solution is the current state
44
//! of the solution being built by the algorithm.
55
6-
use crate::internal::incompatibility::PackageTerm;
6+
use crate::internal::assignment::Assignment::{self, Decision, Derivation};
7+
use crate::internal::incompatibility::{Incompatibility, Relation};
78
use crate::internal::memory::Memory;
89
use crate::package::Package;
10+
use crate::range::Range;
911
use crate::term::Term;
1012
use crate::type_aliases::{Map, SelectedDependencies};
1113
use crate::version::Version;
12-
use crate::{
13-
error::PubGrubError,
14-
internal::incompatibility::{Incompatibility, Relation},
15-
};
16-
use crate::{
17-
internal::assignment::Assignment::{self, Decision, Derivation},
18-
solver::DependencyProvider,
19-
};
2014

2115
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
2216
pub struct DecisionLevel(u32);
@@ -99,6 +93,11 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
9993
self.memory.extract_solution()
10094
}
10195

96+
/// Compute, cache and retrieve the intersection of all terms for this package.
97+
pub fn term_intersection_for_package(&mut self, package: &P) -> Option<&Term<V>> {
98+
self.memory.term_intersection_for_package(package)
99+
}
100+
102101
/// Backtrack the partial solution to a given decision level.
103102
pub fn backtrack(&mut self, decision_level: DecisionLevel) {
104103
// TODO: improve with dichotomic search.
@@ -121,49 +120,15 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
121120
partial_solution
122121
}
123122

124-
/// Heuristic to pick the next package to add to the partial solution.
125-
/// This should be a package with a positive derivation but no decision yet.
126-
/// If multiple choices are possible, use a heuristic.
127-
///
128-
/// Current heuristic employed by this and Pub's implementations is to choose
129-
/// the package with the fewest versions matching the outstanding constraint.
130-
/// This tends to find conflicts earlier if any exist,
131-
/// since these packages will run out of versions to try more quickly.
132-
pub fn pick_package(
133-
&mut self,
134-
dependency_provider: &impl DependencyProvider<P, V>,
135-
) -> Result<Option<PackageTerm<P, V>>, PubGrubError<P, V>> {
136-
let mut out: Option<PackageTerm<P, V>> = None;
137-
let mut min_key = usize::MAX;
138-
for (p, term) in self.memory.potential_packages() {
139-
let key = dependency_provider
140-
.list_available_versions(p)
141-
.map_err(|err| PubGrubError::ErrorRetrievingVersions {
142-
package: p.clone(),
143-
source: err,
144-
})?
145-
.iter()
146-
.filter(|&v| term.contains(v))
147-
.count();
148-
if key < min_key {
149-
min_key = key;
150-
out = Some((p.clone(), term.clone()));
151-
}
123+
/// Extract potential packages for the next iteration of unit propagation.
124+
/// Return `None` if there is no suitable package anymore, which stops the algorithm.
125+
pub fn potential_packages(&mut self) -> Option<impl Iterator<Item = (&P, &Range<V>)>> {
126+
let mut iter = self.memory.potential_packages().peekable();
127+
if iter.peek().is_some() {
128+
Some(iter)
129+
} else {
130+
None
152131
}
153-
Ok(out)
154-
}
155-
156-
/// Pub chooses the latest matching version of the package
157-
/// that match the outstanding constraint.
158-
///
159-
/// Here we just pick the first one that satisfies the terms.
160-
/// It is the responsibility of the provider of `available_versions`
161-
/// to list them with preferred versions first.
162-
pub fn pick_version(available_versions: &[V], partial_solution_term: &Term<V>) -> Option<V> {
163-
available_versions
164-
.iter()
165-
.find(|v| partial_solution_term.contains(v))
166-
.cloned()
167132
}
168133

169134
/// We can add the version to the partial solution as a decision

src/lib.rs

+17-8
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@
7777
//! ```
7878
//! # use pubgrub::solver::{DependencyProvider, Dependencies};
7979
//! # use pubgrub::version::SemanticVersion;
80+
//! # use pubgrub::range::Range;
81+
//! # use pubgrub::type_aliases::Map;
8082
//! # use std::error::Error;
83+
//! # use std::borrow::Borrow;
8184
//! #
8285
//! # struct MyDependencyProvider;
8386
//! #
8487
//! impl DependencyProvider<String, SemanticVersion> for MyDependencyProvider {
85-
//! fn list_available_versions(
86-
//! &self,
87-
//! package: &String
88-
//! ) -> Result<Vec<SemanticVersion>, Box<dyn Error>> {
88+
//! fn choose_package_version<T: Borrow<String>, U: Borrow<Range<SemanticVersion>>>(&self,packages: impl Iterator<Item=(T, U)>) -> Result<(T, Option<SemanticVersion>), Box<dyn Error>> {
8989
//! unimplemented!()
9090
//! }
9191
//!
@@ -98,11 +98,20 @@
9898
//! }
9999
//! }
100100
//! ```
101+
//!
101102
//! The first method
102-
//! [list_available_versions](crate::solver::DependencyProvider::list_available_versions)
103-
//! should return all available versions of a package.
104-
//! The second method
105-
//! [get_dependencies](crate::solver::DependencyProvider::get_dependencies)
103+
//! [choose_package_version](crate::solver::DependencyProvider::choose_package_version)
104+
//! chooses a package and available version compatible with the provided options.
105+
//! A helper function
106+
//! [choose_package_with_fewest_versions](crate::solver::choose_package_with_fewest_versions)
107+
//! is provided for convenience
108+
//! in cases when lists of available versions for packages are easily obtained.
109+
//! The strategy of that helper function consists in choosing the package
110+
//! with the fewest number of compatible versions to speed up resolution.
111+
//! But in general you are free to employ whatever strategy suits you best
112+
//! to pick a package and a version.
113+
//!
114+
//! The second method [get_dependencies](crate::solver::DependencyProvider::get_dependencies)
106115
//! aims at retrieving the dependencies of a given package at a given version.
107116
//! Returns [None] if dependencies are unknown.
108117
//!

0 commit comments

Comments
 (0)