mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Add PubGrub's priority queue (#221)
Pulls in https://github.com/pubgrub-rs/pubgrub/pull/104.
This commit is contained in:
parent
4209e77c95
commit
2ba85bf80e
16 changed files with 369 additions and 278 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -1959,6 +1959,16 @@ dependencies = [
|
|||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "priority-queue"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"indexmap 1.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
|
@ -2011,7 +2021,9 @@ dependencies = [
|
|||
name = "pubgrub"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"indexmap 2.0.2",
|
||||
"log",
|
||||
"priority-queue",
|
||||
"rustc-hash",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::pubgrub::specifier::PubGrubSpecifier;
|
|||
use crate::pubgrub::version::{PubGrubVersion, MAX_VERSION};
|
||||
|
||||
pub(crate) mod package;
|
||||
pub(crate) mod priority;
|
||||
mod specifier;
|
||||
pub(crate) mod version;
|
||||
|
||||
|
|
3
crates/puffin-resolver/src/pubgrub/priority.rs
Normal file
3
crates/puffin-resolver/src/pubgrub/priority.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
use std::cmp::Reverse;
|
||||
|
||||
pub(crate) type PubGrubPriority = Reverse<usize>;
|
|
@ -1,8 +1,8 @@
|
|||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use colored::Colorize;
|
||||
use fxhash::FxHashMap;
|
||||
use petgraph::visit::EdgeRef;
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use pubgrub::range::Range;
|
||||
use pubgrub::solver::{Kind, State};
|
||||
use pubgrub::type_aliases::SelectedDependencies;
|
||||
|
@ -13,6 +13,7 @@ use puffin_client::File;
|
|||
use puffin_package::package_name::PackageName;
|
||||
|
||||
use crate::pubgrub::package::PubGrubPackage;
|
||||
use crate::pubgrub::priority::PubGrubPriority;
|
||||
use crate::pubgrub::version::PubGrubVersion;
|
||||
|
||||
/// A package pinned at a specific version.
|
||||
|
@ -95,7 +96,7 @@ impl Graph {
|
|||
pub fn from_state(
|
||||
selection: &SelectedDependencies<PubGrubPackage, PubGrubVersion>,
|
||||
pins: &FxHashMap<PackageName, FxHashMap<Version, File>>,
|
||||
state: &State<PubGrubPackage, Range<PubGrubVersion>>,
|
||||
state: &State<PubGrubPackage, Range<PubGrubVersion>, PubGrubPriority>,
|
||||
) -> Self {
|
||||
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
||||
// write our own graph, given that our requirements are so simple.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Given a set of requirements, find a set of compatible packages.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::future::Future;
|
||||
|
@ -34,6 +34,7 @@ use crate::distribution::{DistributionFile, SdistFile, WheelFile};
|
|||
use crate::error::ResolveError;
|
||||
use crate::manifest::Manifest;
|
||||
use crate::pubgrub::package::PubGrubPackage;
|
||||
use crate::pubgrub::priority::PubGrubPriority;
|
||||
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
||||
use crate::pubgrub::{iter_requirements, version_range};
|
||||
use crate::resolution::Graph;
|
||||
|
@ -130,32 +131,41 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
|||
// Run unit propagation.
|
||||
state.unit_propagation(next)?;
|
||||
|
||||
// Fetch the list of candidates.
|
||||
let Some(potential_packages) = state.partial_solution.potential_packages() else {
|
||||
let Some(selection) = state.partial_solution.extract_solution() else {
|
||||
return Err(PubGrubError::Failure(
|
||||
"How did we end up with no package to choose but no solution?".into(),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
return Ok(Graph::from_state(&selection, &pins, &state));
|
||||
};
|
||||
// Pre-visit all candidate packages, to allow metadata to be fetched in parallel.
|
||||
self.pre_visit(
|
||||
state.partial_solution.prioritized_packages(),
|
||||
&mut requested_versions,
|
||||
request_sink,
|
||||
)?;
|
||||
|
||||
// Choose a package version.
|
||||
let potential_packages = potential_packages.collect::<Vec<_>>();
|
||||
let Some(highest_priority_pkg) = state
|
||||
.partial_solution
|
||||
.pick_highest_priority_pkg(|package, range| self.prioritize(package, range))
|
||||
else {
|
||||
let selection = state.partial_solution.extract_solution();
|
||||
return Ok(Graph::from_state(&selection, &pins, &state));
|
||||
};
|
||||
next = highest_priority_pkg;
|
||||
|
||||
let term_intersection = state
|
||||
.partial_solution
|
||||
.term_intersection_for_package(&next)
|
||||
.ok_or_else(|| {
|
||||
PubGrubError::Failure("a package was chosen but we don't have a term.".into())
|
||||
})?;
|
||||
let decision = self
|
||||
.choose_package_version(
|
||||
potential_packages,
|
||||
.choose_version(
|
||||
&next,
|
||||
term_intersection.unwrap_positive(),
|
||||
&mut pins,
|
||||
&mut requested_versions,
|
||||
request_sink,
|
||||
)
|
||||
.await?;
|
||||
next = decision.0.clone();
|
||||
|
||||
// Pick the next compatible version.
|
||||
let version = match decision.1 {
|
||||
let version = match decision {
|
||||
None => {
|
||||
debug!("No compatible version found for: {}", next);
|
||||
|
||||
|
@ -226,19 +236,28 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given a set of candidate packages, choose the next package (and version) to add to the
|
||||
/// partial solution.
|
||||
async fn choose_package_version<T: Borrow<PubGrubPackage>, U: Borrow<Range<PubGrubVersion>>>(
|
||||
#[allow(clippy::unused_self)]
|
||||
fn prioritize(
|
||||
&self,
|
||||
mut potential_packages: Vec<(T, U)>,
|
||||
pins: &mut FxHashMap<PackageName, FxHashMap<pep440_rs::Version, File>>,
|
||||
_package: &PubGrubPackage,
|
||||
_range: &Range<PubGrubVersion>,
|
||||
) -> PubGrubPriority {
|
||||
// TODO(charlie): Define a priority function.
|
||||
Reverse(0)
|
||||
}
|
||||
|
||||
/// Visit the set of candidate packages prior to selection. This allows us to fetch metadata for
|
||||
/// all of the packages in parallel.
|
||||
fn pre_visit(
|
||||
&self,
|
||||
packages: impl Iterator<Item = (&'a PubGrubPackage, &'a Range<PubGrubVersion>)>,
|
||||
in_flight: &mut FxHashSet<String>,
|
||||
request_sink: &futures::channel::mpsc::UnboundedSender<Request>,
|
||||
) -> Result<(T, Option<PubGrubVersion>), ResolveError> {
|
||||
) -> Result<(), ResolveError> {
|
||||
// Iterate over the potential packages, and fetch file metadata for any of them. These
|
||||
// represent our current best guesses for the versions that we _might_ select.
|
||||
for (index, (package, range)) in potential_packages.iter().enumerate() {
|
||||
let PubGrubPackage::Package(package_name, _) = package.borrow() else {
|
||||
for (package, range) in packages {
|
||||
let PubGrubPackage::Package(package_name, _) = package else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -250,13 +269,9 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
|||
|
||||
// Try to find a compatible version. If there aren't any compatible versions,
|
||||
// short-circuit and return `None`.
|
||||
let Some(candidate) = self
|
||||
.selector
|
||||
.select(package_name, range.borrow(), version_map)
|
||||
else {
|
||||
// Short circuit: we couldn't find _any_ compatible versions for a package.
|
||||
let (package, _range) = potential_packages.swap_remove(index);
|
||||
return Ok((package, None));
|
||||
let Some(candidate) = self.selector.select(package_name, range, version_map) else {
|
||||
// Short-circuit: we couldn't find _any_ compatible versions for a package.
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Emit a request to fetch the metadata for this version.
|
||||
|
@ -277,14 +292,21 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Always choose the first package.
|
||||
// TODO(charlie): Devise a better strategy here (for example: always choose the package with
|
||||
// the fewest versions).
|
||||
let (package, range) = potential_packages.swap_remove(0);
|
||||
|
||||
return match package.borrow() {
|
||||
PubGrubPackage::Root => Ok((package, Some(MIN_VERSION.clone()))),
|
||||
/// Given a set of candidate packages, choose the next package (and version) to add to the
|
||||
/// partial solution.
|
||||
async fn choose_version(
|
||||
&self,
|
||||
package: &PubGrubPackage,
|
||||
range: &Range<PubGrubVersion>,
|
||||
pins: &mut FxHashMap<PackageName, FxHashMap<pep440_rs::Version, File>>,
|
||||
in_flight: &mut FxHashSet<String>,
|
||||
request_sink: &futures::channel::mpsc::UnboundedSender<Request>,
|
||||
) -> Result<Option<PubGrubVersion>, ResolveError> {
|
||||
return match package {
|
||||
PubGrubPackage::Root => Ok(Some(MIN_VERSION.clone())),
|
||||
PubGrubPackage::Package(package_name, _) => {
|
||||
// Wait for the metadata to be available.
|
||||
let entry = self.index.packages.wait(package_name).await.unwrap();
|
||||
|
@ -292,17 +314,13 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
|||
|
||||
debug!(
|
||||
"Searching for a compatible version of {} ({})",
|
||||
package_name,
|
||||
range.borrow(),
|
||||
package_name, range,
|
||||
);
|
||||
|
||||
// Find a compatible version.
|
||||
let Some(candidate) =
|
||||
self.selector
|
||||
.select(package_name, range.borrow(), version_map)
|
||||
else {
|
||||
let Some(candidate) = self.selector.select(package_name, range, version_map) else {
|
||||
// Short circuit: we couldn't find _any_ compatible versions for a package.
|
||||
return Ok((package, None));
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
debug!(
|
||||
|
@ -340,7 +358,7 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
|||
}
|
||||
|
||||
let version = candidate.version.clone();
|
||||
Ok((package, Some(version)))
|
||||
Ok(Some(version))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,25 +6,3 @@ pygls>=1.0.1
|
|||
lsprotocol>=2023.0.0a1
|
||||
ruff>=0.0.274
|
||||
typing_extensions
|
||||
scipy
|
||||
numpy
|
||||
pandas<2.0.0
|
||||
matplotlib>=3.0.0
|
||||
scikit-learn
|
||||
rich
|
||||
textual
|
||||
jupyter>=1.0.0,<2.0.0
|
||||
transformers[torch]
|
||||
django<4.0.0
|
||||
sqlalchemy
|
||||
psycopg2-binary
|
||||
trio<0.20
|
||||
trio-websocket
|
||||
trio-asyncio
|
||||
trio-typing
|
||||
trio-protocol
|
||||
fastapi
|
||||
typer
|
||||
pydantic
|
||||
uvicorn
|
||||
traitlets
|
2
vendor/pubgrub/Cargo.toml
vendored
2
vendor/pubgrub/Cargo.toml
vendored
|
@ -22,6 +22,8 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples
|
|||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
rustc-hash = "1.1.0"
|
||||
indexmap = "2.0.2"
|
||||
priority-queue = "1.1.1"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
log = "0.4.14" # for debug logs in tests
|
||||
|
||||
|
|
|
@ -32,13 +32,6 @@ impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>>
|
|||
impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvider<P, VS>
|
||||
for CachingDependencyProvider<P, VS, DP>
|
||||
{
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
|
||||
&self,
|
||||
packages: impl Iterator<Item = (T, U)>,
|
||||
) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
|
||||
self.remote_dependencies.choose_package_version(packages)
|
||||
}
|
||||
|
||||
// Caches dependencies if they were already queried
|
||||
fn get_dependencies(
|
||||
&self,
|
||||
|
@ -66,6 +59,20 @@ impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvid
|
|||
error @ Err(_) => error,
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_version(
|
||||
&self,
|
||||
package: &P,
|
||||
range: &VS,
|
||||
) -> Result<Option<VS::V>, Box<dyn Error + Send + Sync>> {
|
||||
self.remote_dependencies.choose_version(package, range)
|
||||
}
|
||||
|
||||
type Priority = DP::Priority;
|
||||
|
||||
fn prioritize(&self, package: &P, range: &VS) -> Self::Priority {
|
||||
self.remote_dependencies.prioritize(package, range)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
2
vendor/pubgrub/src/error.rs
vendored
2
vendor/pubgrub/src/error.rs
vendored
|
@ -46,7 +46,7 @@ pub enum PubGrubError<P: Package, VS: VersionSet> {
|
|||
/// Error arising when the implementer of
|
||||
/// [DependencyProvider](crate::solver::DependencyProvider)
|
||||
/// returned an error in the method
|
||||
/// [choose_package_version](crate::solver::DependencyProvider::choose_package_version).
|
||||
/// [choose_version](crate::solver::DependencyProvider::choose_version).
|
||||
#[error("Decision making failed")]
|
||||
ErrorChoosingPackageVersion(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
|
|
6
vendor/pubgrub/src/internal/core.rs
vendored
6
vendor/pubgrub/src/internal/core.rs
vendored
|
@ -20,7 +20,7 @@ use crate::version_set::VersionSet;
|
|||
|
||||
/// Current state of the PubGrub algorithm.
|
||||
#[derive(Clone)]
|
||||
pub struct State<P: Package, VS: VersionSet> {
|
||||
pub struct State<P: Package, VS: VersionSet, Priority: Ord + Clone> {
|
||||
root_package: P,
|
||||
root_version: VS::V,
|
||||
|
||||
|
@ -32,7 +32,7 @@ pub struct State<P: Package, VS: VersionSet> {
|
|||
|
||||
/// Partial solution.
|
||||
/// TODO: remove pub.
|
||||
pub partial_solution: PartialSolution<P, VS>,
|
||||
pub partial_solution: PartialSolution<P, VS, Priority>,
|
||||
|
||||
/// The store is the reference storage for all incompatibilities.
|
||||
pub incompatibility_store: Arena<Incompatibility<P, VS>>,
|
||||
|
@ -43,7 +43,7 @@ pub struct State<P: Package, VS: VersionSet> {
|
|||
unit_propagation_buffer: SmallVec<P>,
|
||||
}
|
||||
|
||||
impl<P: Package, VS: VersionSet> State<P, VS> {
|
||||
impl<P: Package, VS: VersionSet, Priority: Ord + Clone> State<P, VS, Priority> {
|
||||
/// Initialization of PubGrub state.
|
||||
pub fn init(root_package: P, root_version: VS::V) -> Self {
|
||||
let mut incompatibility_store = Arena::new();
|
||||
|
|
161
vendor/pubgrub/src/internal/partial_solution.rs
vendored
161
vendor/pubgrub/src/internal/partial_solution.rs
vendored
|
@ -1,20 +1,26 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! A Memory acts like a structured partial solution
|
||||
//! where terms are regrouped by package in a [Map].
|
||||
//! where terms are regrouped by package in a [Map](crate::type_aliases::Map).
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use priority_queue::PriorityQueue;
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::internal::arena::Arena;
|
||||
use crate::internal::incompatibility::{IncompId, Incompatibility, Relation};
|
||||
use crate::internal::small_map::SmallMap;
|
||||
use crate::package::Package;
|
||||
use crate::term::Term;
|
||||
use crate::type_aliases::{Map, SelectedDependencies};
|
||||
use crate::type_aliases::SelectedDependencies;
|
||||
use crate::version_set::VersionSet;
|
||||
|
||||
use super::small_vec::SmallVec;
|
||||
|
||||
type FnvIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct DecisionLevel(pub u32);
|
||||
|
||||
|
@ -27,13 +33,29 @@ impl DecisionLevel {
|
|||
/// The partial solution contains all package assignments,
|
||||
/// organized by package and historically ordered.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PartialSolution<P: Package, VS: VersionSet> {
|
||||
pub struct PartialSolution<P: Package, VS: VersionSet, Priority: Ord + Clone> {
|
||||
next_global_index: u32,
|
||||
current_decision_level: DecisionLevel,
|
||||
package_assignments: Map<P, PackageAssignments<P, VS>>,
|
||||
/// `package_assignments` is primarily a HashMap from a package to its
|
||||
/// `PackageAssignments`. But it can also keep the items in an order.
|
||||
/// We maintain three sections in this order:
|
||||
/// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`.
|
||||
/// This makes it very efficient to extract the solution, And to backtrack to a particular decision level.
|
||||
/// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments
|
||||
/// changed since the last time `prioritize` has bean called. Within this range there is no sorting.
|
||||
/// 3. `[changed_this_decision_level..]` Containes all packages that **have** had there assignments changed since
|
||||
/// the last time `prioritize` has bean called. The inverse is not necessarily true, some packages in the range
|
||||
/// did not have a change. Within this range there is no sorting.
|
||||
package_assignments: FnvIndexMap<P, PackageAssignments<P, VS>>,
|
||||
/// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment
|
||||
/// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order.
|
||||
prioritized_potential_packages: PriorityQueue<P, Priority, BuildHasherDefault<FxHasher>>,
|
||||
changed_this_decision_level: usize,
|
||||
}
|
||||
|
||||
impl<P: Package, VS: VersionSet> Display for PartialSolution<P, VS> {
|
||||
impl<P: Package, VS: VersionSet, Priority: Ord + Clone> Display
|
||||
for PartialSolution<P, VS, Priority>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut assignments: Vec<_> = self
|
||||
.package_assignments
|
||||
|
@ -120,13 +142,15 @@ pub enum SatisfierSearch<P: Package, VS: VersionSet> {
|
|||
},
|
||||
}
|
||||
|
||||
impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
||||
impl<P: Package, VS: VersionSet, Priority: Ord + Clone> PartialSolution<P, VS, Priority> {
|
||||
/// Initialize an empty PartialSolution.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
next_global_index: 0,
|
||||
current_decision_level: DecisionLevel(0),
|
||||
package_assignments: Map::default(),
|
||||
package_assignments: FnvIndexMap::default(),
|
||||
prioritized_potential_packages: PriorityQueue::default(),
|
||||
changed_this_decision_level: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,21 +165,20 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
AssignmentsIntersection::Decision(_) => panic!("Already existing decision"),
|
||||
// Cannot be called if the versions is not contained in the terms intersection.
|
||||
AssignmentsIntersection::Derivations(term) => {
|
||||
debug_assert!(
|
||||
term.contains(&version),
|
||||
"{}: {} was expected to be contained in {}",
|
||||
package,
|
||||
version,
|
||||
term,
|
||||
)
|
||||
debug_assert!(term.contains(&version))
|
||||
}
|
||||
},
|
||||
}
|
||||
assert_eq!(
|
||||
self.changed_this_decision_level,
|
||||
self.package_assignments.len()
|
||||
);
|
||||
}
|
||||
let new_idx = self.current_decision_level.0 as usize;
|
||||
self.current_decision_level = self.current_decision_level.increment();
|
||||
let pa = self
|
||||
let (old_idx, _, pa) = self
|
||||
.package_assignments
|
||||
.get_mut(&package)
|
||||
.get_full_mut(&package)
|
||||
.expect("Derivations must already exist");
|
||||
pa.highest_decision_level = self.current_decision_level;
|
||||
pa.assignments_intersection = AssignmentsIntersection::Decision((
|
||||
|
@ -163,6 +186,10 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
version.clone(),
|
||||
Term::exact(version),
|
||||
));
|
||||
// Maintain that the beginning of the `package_assignments` Have all decisions in sorted order.
|
||||
if new_idx != old_idx {
|
||||
self.package_assignments.swap_indices(new_idx, old_idx);
|
||||
}
|
||||
self.next_global_index += 1;
|
||||
}
|
||||
|
||||
|
@ -173,7 +200,7 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
cause: IncompId<P, VS>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) {
|
||||
use std::collections::hash_map::Entry;
|
||||
use indexmap::map::Entry;
|
||||
let term = store[cause].get(&package).unwrap().negate();
|
||||
let dated_derivation = DatedDerivation {
|
||||
global_index: self.next_global_index,
|
||||
|
@ -181,8 +208,10 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
cause,
|
||||
};
|
||||
self.next_global_index += 1;
|
||||
let pa_last_index = self.package_assignments.len().saturating_sub(1);
|
||||
match self.package_assignments.entry(package) {
|
||||
Entry::Occupied(mut occupied) => {
|
||||
let idx = occupied.index();
|
||||
let pa = occupied.get_mut();
|
||||
pa.highest_decision_level = self.current_decision_level;
|
||||
match &mut pa.assignments_intersection {
|
||||
|
@ -192,11 +221,21 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
}
|
||||
AssignmentsIntersection::Derivations(t) => {
|
||||
*t = t.intersection(&term);
|
||||
if t.is_positive() {
|
||||
// we can use `swap_indices` to make `changed_this_decision_level` only go down by 1
|
||||
// but the copying is slower then the larger search
|
||||
self.changed_this_decision_level =
|
||||
std::cmp::min(self.changed_this_decision_level, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
pa.dated_derivations.push(dated_derivation);
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
if term.is_positive() {
|
||||
self.changed_this_decision_level =
|
||||
std::cmp::min(self.changed_this_decision_level, pa_last_index);
|
||||
}
|
||||
v.insert(PackageAssignments {
|
||||
smallest_decision_level: self.current_decision_level,
|
||||
highest_decision_level: self.current_decision_level,
|
||||
|
@ -207,43 +246,66 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Extract potential packages for the next iteration of unit propagation.
|
||||
/// Return `None` if there is no suitable package anymore, which stops the algorithm.
|
||||
/// A package is a potential pick if there isn't an already
|
||||
/// selected version (no "decision")
|
||||
/// and if it contains at least one positive derivation term
|
||||
/// in the partial solution.
|
||||
pub fn potential_packages(&self) -> Option<impl Iterator<Item = (&P, &VS)>> {
|
||||
let mut iter = self
|
||||
.package_assignments
|
||||
pub fn prioritized_packages(&self) -> impl Iterator<Item = (&P, &VS)> {
|
||||
let check_all = self.changed_this_decision_level
|
||||
== self.current_decision_level.0.saturating_sub(1) as usize;
|
||||
let current_decision_level = self.current_decision_level;
|
||||
self.package_assignments
|
||||
.get_range(self.changed_this_decision_level..)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(move |(_, pa)| {
|
||||
// We only actually need to update the package if its Been changed
|
||||
// since the last time we called prioritize.
|
||||
// Which means it's highest decision level is the current decision level,
|
||||
// or if we backtracked in the mean time.
|
||||
check_all || pa.highest_decision_level == current_decision_level
|
||||
})
|
||||
.filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p))
|
||||
.peekable();
|
||||
if iter.peek().is_some() {
|
||||
Some(iter)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pick_highest_priority_pkg(
|
||||
&mut self,
|
||||
prioritizer: impl Fn(&P, &VS) -> Priority,
|
||||
) -> Option<P> {
|
||||
let check_all = self.changed_this_decision_level
|
||||
== self.current_decision_level.0.saturating_sub(1) as usize;
|
||||
let current_decision_level = self.current_decision_level;
|
||||
let prioritized_potential_packages = &mut self.prioritized_potential_packages;
|
||||
self.package_assignments
|
||||
.get_range(self.changed_this_decision_level..)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|(_, pa)| {
|
||||
// We only actually need to update the package if its Been changed
|
||||
// since the last time we called prioritize.
|
||||
// Which means it's highest decision level is the current decision level,
|
||||
// or if we backtracked in the mean time.
|
||||
check_all || pa.highest_decision_level == current_decision_level
|
||||
})
|
||||
.filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p))
|
||||
.for_each(|(p, r)| {
|
||||
let priority = prioritizer(p, r);
|
||||
prioritized_potential_packages.push(p.clone(), priority);
|
||||
});
|
||||
self.changed_this_decision_level = self.package_assignments.len();
|
||||
prioritized_potential_packages.pop().map(|(p, _)| p)
|
||||
}
|
||||
|
||||
/// If a partial solution has, for every positive derivation,
|
||||
/// a corresponding decision that satisfies that assignment,
|
||||
/// it's a total solution and version solving has succeeded.
|
||||
pub fn extract_solution(&self) -> Option<SelectedDependencies<P, VS::V>> {
|
||||
let mut solution = Map::default();
|
||||
for (p, pa) in &self.package_assignments {
|
||||
match &pa.assignments_intersection {
|
||||
AssignmentsIntersection::Decision((_, v, _)) => {
|
||||
solution.insert(p.clone(), v.clone());
|
||||
pub fn extract_solution(&self) -> SelectedDependencies<P, VS::V> {
|
||||
self.package_assignments
|
||||
.iter()
|
||||
.take(self.current_decision_level.0 as usize)
|
||||
.map(|(p, pa)| match &pa.assignments_intersection {
|
||||
AssignmentsIntersection::Decision((_, v, _)) => (p.clone(), v.clone()),
|
||||
AssignmentsIntersection::Derivations(_) => {
|
||||
panic!("Derivations in the Decision part")
|
||||
}
|
||||
AssignmentsIntersection::Derivations(term) => {
|
||||
if term.is_positive() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(solution)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Backtrack the partial solution to a given decision level.
|
||||
|
@ -290,6 +352,9 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
true
|
||||
}
|
||||
});
|
||||
// Throw away all stored priority levels, And mark that they all need to be recomputed.
|
||||
self.prioritized_potential_packages.clear();
|
||||
self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize;
|
||||
}
|
||||
|
||||
/// We can add the version to the partial solution as a decision
|
||||
|
@ -386,7 +451,7 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
/// to return a coherent previous_satisfier_level.
|
||||
fn find_satisfier(
|
||||
incompat: &Incompatibility<P, VS>,
|
||||
package_assignments: &Map<P, PackageAssignments<P, VS>>,
|
||||
package_assignments: &FnvIndexMap<P, PackageAssignments<P, VS>>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) -> SmallMap<P, (usize, u32, DecisionLevel)> {
|
||||
let mut satisfied = SmallMap::Empty;
|
||||
|
@ -407,7 +472,7 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
|
|||
incompat: &Incompatibility<P, VS>,
|
||||
satisfier_package: &P,
|
||||
mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>,
|
||||
package_assignments: &Map<P, PackageAssignments<P, VS>>,
|
||||
package_assignments: &FnvIndexMap<P, PackageAssignments<P, VS>>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) -> DecisionLevel {
|
||||
// First, let's retrieve the previous derivations and the initial accum_term.
|
||||
|
|
26
vendor/pubgrub/src/lib.rs
vendored
26
vendor/pubgrub/src/lib.rs
vendored
|
@ -75,7 +75,7 @@
|
|||
//! trait for our own type.
|
||||
//! Let's say that we will use [String] for packages,
|
||||
//! and [SemanticVersion](version::SemanticVersion) for versions.
|
||||
//! This may be done quite easily by implementing the two following functions.
|
||||
//! This may be done quite easily by implementing the three following functions.
|
||||
//! ```
|
||||
//! # use pubgrub::solver::{DependencyProvider, Dependencies};
|
||||
//! # use pubgrub::version::SemanticVersion;
|
||||
|
@ -89,7 +89,12 @@
|
|||
//! type SemVS = Range<SemanticVersion>;
|
||||
//!
|
||||
//! impl DependencyProvider<String, SemVS> for MyDependencyProvider {
|
||||
//! fn choose_package_version<T: Borrow<String>, U: Borrow<SemVS>>(&self,packages: impl Iterator<Item=(T, U)>) -> Result<(T, Option<SemanticVersion>), Box<dyn Error + Send + Sync>> {
|
||||
//! fn choose_version(&self, package: &String, range: &SemVS) -> Result<Option<SemanticVersion>, Box<dyn Error + Send + Sync>> {
|
||||
//! unimplemented!()
|
||||
//! }
|
||||
//!
|
||||
//! type Priority = usize;
|
||||
//! fn prioritize(&self, package: &String, range: &SemVS) -> Self::Priority {
|
||||
//! unimplemented!()
|
||||
//! }
|
||||
//!
|
||||
|
@ -104,18 +109,17 @@
|
|||
//! ```
|
||||
//!
|
||||
//! The first method
|
||||
//! [choose_package_version](crate::solver::DependencyProvider::choose_package_version)
|
||||
//! chooses a package and available version compatible with the provided options.
|
||||
//! A helper function
|
||||
//! [choose_package_with_fewest_versions](crate::solver::choose_package_with_fewest_versions)
|
||||
//! is provided for convenience
|
||||
//! in cases when lists of available versions for packages are easily obtained.
|
||||
//! The strategy of that helper function consists in choosing the package
|
||||
//! with the fewest number of compatible versions to speed up resolution.
|
||||
//! [choose_version](crate::solver::DependencyProvider::choose_version)
|
||||
//! chooses a version compatible with the provided range for a package.
|
||||
//! The second method
|
||||
//! [prioritize](crate::solver::DependencyProvider::prioritize)
|
||||
//! in which order different packages should be chosen.
|
||||
//! Usually prioritizing packages
|
||||
//! with the fewest number of compatible versions speeds up resolution.
|
||||
//! But in general you are free to employ whatever strategy suits you best
|
||||
//! to pick a package and a version.
|
||||
//!
|
||||
//! The second method [get_dependencies](crate::solver::DependencyProvider::get_dependencies)
|
||||
//! The third method [get_dependencies](crate::solver::DependencyProvider::get_dependencies)
|
||||
//! aims at retrieving the dependencies of a given package at a given version.
|
||||
//! Returns [None] if dependencies are unknown.
|
||||
//!
|
||||
|
|
212
vendor/pubgrub/src/solver.rs
vendored
212
vendor/pubgrub/src/solver.rs
vendored
|
@ -68,7 +68,7 @@
|
|||
//! to satisfy the dependencies of that package and version pair.
|
||||
//! If there is no solution, the reason will be provided as clear as possible.
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::{BTreeMap, BTreeSet as Set};
|
||||
use std::error::Error;
|
||||
|
||||
|
@ -103,30 +103,27 @@ pub fn resolve<P: Package, VS: VersionSet>(
|
|||
state.partial_solution
|
||||
);
|
||||
|
||||
let Some(potential_packages) = state.partial_solution.potential_packages() else {
|
||||
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 Some(highest_priority_pkg) = state
|
||||
.partial_solution
|
||||
.pick_highest_priority_pkg(|p, r| dependency_provider.prioritize(p, r))
|
||||
else {
|
||||
return Ok(state.partial_solution.extract_solution());
|
||||
};
|
||||
next = highest_priority_pkg;
|
||||
|
||||
let decision = dependency_provider
|
||||
.choose_package_version(potential_packages)
|
||||
.map_err(PubGrubError::ErrorChoosingPackageVersion)?;
|
||||
info!("DP chose: {} @ {:?}", decision.0, decision.1);
|
||||
|
||||
next = decision.0.clone();
|
||||
|
||||
// Pick the next compatible version.
|
||||
let term_intersection = state
|
||||
.partial_solution
|
||||
.term_intersection_for_package(&next)
|
||||
.ok_or_else(|| {
|
||||
PubGrubError::Failure("a package was chosen but we don't have a term.".into())
|
||||
})?;
|
||||
let decision = dependency_provider
|
||||
.choose_version(&next, term_intersection.unwrap_positive())
|
||||
.map_err(PubGrubError::ErrorChoosingPackageVersion)?;
|
||||
info!("DP chose: {} @ {:?}", next, decision);
|
||||
|
||||
let v = match decision.1 {
|
||||
// Pick the next compatible version.
|
||||
let v = match decision {
|
||||
None => {
|
||||
let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone());
|
||||
state.add_incompatibility(inc);
|
||||
|
@ -146,51 +143,53 @@ pub fn resolve<P: Package, VS: VersionSet>(
|
|||
.or_default()
|
||||
.insert(v.clone());
|
||||
|
||||
if !is_new_dependency {
|
||||
if is_new_dependency {
|
||||
// Retrieve that package dependencies.
|
||||
let p = &next;
|
||||
let dependencies = dependency_provider.get_dependencies(p, &v).map_err(|err| {
|
||||
PubGrubError::ErrorRetrievingDependencies {
|
||||
package: p.clone(),
|
||||
version: v.clone(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
|
||||
let known_dependencies = match dependencies {
|
||||
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(),
|
||||
});
|
||||
}
|
||||
Dependencies::Known(x) => x,
|
||||
};
|
||||
|
||||
// Add that package and version if the dependencies are not problematic.
|
||||
let dep_incompats = state.add_incompatibility_from_dependencies(
|
||||
p.clone(),
|
||||
v.clone(),
|
||||
&known_dependencies,
|
||||
);
|
||||
|
||||
state.partial_solution.add_version(
|
||||
p.clone(),
|
||||
v,
|
||||
dep_incompats,
|
||||
&state.incompatibility_store,
|
||||
);
|
||||
} else {
|
||||
// `dep_incompats` are already in `incompatibilities` so we know there are not satisfied
|
||||
// terms and can add the decision directly.
|
||||
info!("add_decision (not first time): {} @ {}", &next, v);
|
||||
state.partial_solution.add_decision(next.clone(), v);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve that package dependencies.
|
||||
let p = &next;
|
||||
let dependencies = dependency_provider.get_dependencies(p, &v).map_err(|err| {
|
||||
PubGrubError::ErrorRetrievingDependencies {
|
||||
package: p.clone(),
|
||||
version: v.clone(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
|
||||
let known_dependencies = match dependencies {
|
||||
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(),
|
||||
});
|
||||
}
|
||||
Dependencies::Known(x) => x,
|
||||
};
|
||||
|
||||
// Add that package and version if the dependencies are not problematic.
|
||||
let dep_incompats =
|
||||
state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &known_dependencies);
|
||||
|
||||
state.partial_solution.add_version(
|
||||
p.clone(),
|
||||
v,
|
||||
dep_incompats,
|
||||
&state.incompatibility_store,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,11 +209,15 @@ pub trait DependencyProvider<P: Package, VS: VersionSet> {
|
|||
/// [Decision making](https://github.com/dart-lang/pub/blob/master/doc/solver.md#decision-making)
|
||||
/// is the process of choosing the next package
|
||||
/// and version that will be appended to the partial solution.
|
||||
/// Every time such a decision must be made,
|
||||
/// potential valid packages and sets of versions are preselected by the resolver,
|
||||
/// and the dependency provider must choose.
|
||||
///
|
||||
/// The strategy employed to choose such package and version
|
||||
/// Every time such a decision must be made, the resolver looks at all the potential valid
|
||||
/// packages that have changed, and a asks the dependency provider how important each one is.
|
||||
/// For each one it calls `prioritize` with the name of the package and the current set of
|
||||
/// acceptable versions.
|
||||
/// The resolver will then pick the package with the highes priority from all the potential valid
|
||||
/// packages.
|
||||
///
|
||||
/// The strategy employed to prioritize packages
|
||||
/// cannot change the existence of a solution or not,
|
||||
/// but can drastically change the performances of the solver,
|
||||
/// or the properties of the solution.
|
||||
|
@ -227,16 +230,24 @@ pub trait DependencyProvider<P: Package, VS: VersionSet> {
|
|||
/// > since these packages will run out of versions to try more quickly.
|
||||
/// > But there's likely room for improvement in these heuristics.
|
||||
///
|
||||
/// A helper function [choose_package_with_fewest_versions] is provided to ease
|
||||
/// implementations of this method if you can produce an iterator
|
||||
/// of the available versions in preference order for any package.
|
||||
/// Note: the resolver may call this even when the range has not change,
|
||||
/// if it is more efficient for the resolveres internal data structures.
|
||||
fn prioritize(&self, package: &P, range: &VS) -> Self::Priority;
|
||||
/// The type returned from `prioritize`. The resolver does not care what type this is
|
||||
/// as long as it can pick a largest one and clone it.
|
||||
///
|
||||
/// Note: the type `T` ensures that this returns an item from the `packages` argument.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn choose_package_version<T: Borrow<P>, U: Borrow<VS>>(
|
||||
/// [std::cmp::Reverse] can be useful if you want to pick the package with
|
||||
/// the fewest versions that match the outstanding constraint.
|
||||
type Priority: Ord + Clone;
|
||||
|
||||
/// Once the resolver has found the highest `Priority` package from all potential valid
|
||||
/// packages, it needs to know what vertion of that package to use. The most common pattern
|
||||
/// is to select the largest vertion that the range contains.
|
||||
fn choose_version(
|
||||
&self,
|
||||
potential_packages: impl Iterator<Item = (T, U)>,
|
||||
) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>>;
|
||||
package: &P,
|
||||
range: &VS,
|
||||
) -> Result<Option<VS::V>, Box<dyn Error + Send + Sync>>;
|
||||
|
||||
/// Retrieves the package dependencies.
|
||||
/// Return [Dependencies::Unknown] if its dependencies are unknown.
|
||||
|
@ -256,35 +267,6 @@ pub trait DependencyProvider<P: Package, VS: VersionSet> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This is a helper function to make it easy to implement
|
||||
/// [DependencyProvider::choose_package_version].
|
||||
/// It takes a function `list_available_versions` that takes a package and returns an iterator
|
||||
/// of the available versions in preference order.
|
||||
/// The helper finds the package from the `packages` argument with the fewest versions from
|
||||
/// `list_available_versions` contained in the constraints. Then takes that package and finds the
|
||||
/// first version contained in the constraints.
|
||||
pub fn choose_package_with_fewest_versions<P: Package, VS: VersionSet, T, U, I, F>(
|
||||
list_available_versions: F,
|
||||
potential_packages: impl Iterator<Item = (T, U)>,
|
||||
) -> (T, Option<VS::V>)
|
||||
where
|
||||
T: Borrow<P>,
|
||||
U: Borrow<VS>,
|
||||
I: Iterator<Item = VS::V>,
|
||||
F: Fn(&P) -> I,
|
||||
{
|
||||
let count_valid = |(p, set): &(T, U)| {
|
||||
list_available_versions(p.borrow())
|
||||
.filter(|v| set.borrow().contains(v))
|
||||
.count()
|
||||
};
|
||||
let (pkg, set) = potential_packages
|
||||
.min_by_key(count_valid)
|
||||
.expect("potential_packages gave us an empty iterator");
|
||||
let version = list_available_versions(pkg.borrow()).find(|v| set.borrow().contains(v));
|
||||
(pkg, version)
|
||||
}
|
||||
|
||||
/// A basic implementation of [DependencyProvider].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -354,25 +336,29 @@ impl<P: Package, VS: VersionSet> OfflineDependencyProvider<P, VS> {
|
|||
|
||||
/// An implementation of [DependencyProvider] that
|
||||
/// contains all dependency information available in memory.
|
||||
/// Packages are picked with the fewest versions contained in the constraints first.
|
||||
/// Currently packages are picked with the fewest versions contained in the constraints first.
|
||||
/// But, that may change in new versions if better heuristics are found.
|
||||
/// Versions are picked with the newest versions first.
|
||||
impl<P: Package, VS: VersionSet> DependencyProvider<P, VS> for OfflineDependencyProvider<P, VS> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn choose_package_version<T: Borrow<P>, U: Borrow<VS>>(
|
||||
fn choose_version(
|
||||
&self,
|
||||
potential_packages: impl Iterator<Item = (T, U)>,
|
||||
) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
|
||||
Ok(choose_package_with_fewest_versions(
|
||||
|p| {
|
||||
self.dependencies
|
||||
.get(p)
|
||||
.into_iter()
|
||||
.flat_map(|k| k.keys())
|
||||
.rev()
|
||||
.cloned()
|
||||
},
|
||||
potential_packages,
|
||||
))
|
||||
package: &P,
|
||||
range: &VS,
|
||||
) -> Result<Option<VS::V>, Box<dyn Error + Send + Sync>> {
|
||||
Ok(self
|
||||
.dependencies
|
||||
.get(package)
|
||||
.and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned()))
|
||||
}
|
||||
|
||||
type Priority = Reverse<usize>;
|
||||
fn prioritize(&self, package: &P, range: &VS) -> Self::Priority {
|
||||
Reverse(
|
||||
self.dependencies
|
||||
.get(package)
|
||||
.map(|versions| versions.keys().filter(|v| range.contains(v)).count())
|
||||
.unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_dependencies(
|
||||
|
|
2
vendor/pubgrub/src/term.rs
vendored
2
vendor/pubgrub/src/term.rs
vendored
|
@ -64,7 +64,7 @@ impl<VS: VersionSet> Term<VS> {
|
|||
|
||||
/// Unwrap the set contained in a positive term.
|
||||
/// Will panic if used on a negative set.
|
||||
pub(crate) fn unwrap_positive(&self) -> &VS {
|
||||
pub fn unwrap_positive(&self) -> &VS {
|
||||
match self {
|
||||
Self::Positive(set) => set,
|
||||
_ => panic!("Negative term cannot unwrap positive set"),
|
||||
|
|
2
vendor/pubgrub/src/version.rs
vendored
2
vendor/pubgrub/src/version.rs
vendored
|
@ -121,7 +121,7 @@ impl SemanticVersion {
|
|||
}
|
||||
|
||||
/// Error creating [SemanticVersion] from [String].
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum VersionParseError {
|
||||
/// [SemanticVersion] must contain major, minor, patch versions.
|
||||
#[error("version {full_version} must contain 3 numbers separated by dot")]
|
||||
|
|
56
vendor/pubgrub/tests/proptest.rs
vendored
56
vendor/pubgrub/tests/proptest.rs
vendored
|
@ -6,10 +6,7 @@ use pubgrub::error::PubGrubError;
|
|||
use pubgrub::package::Package;
|
||||
use pubgrub::range::Range;
|
||||
use pubgrub::report::{DefaultStringReporter, Reporter};
|
||||
use pubgrub::solver::{
|
||||
choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider,
|
||||
OfflineDependencyProvider,
|
||||
};
|
||||
use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider};
|
||||
use pubgrub::version::{NumberVersion, SemanticVersion};
|
||||
use pubgrub::version_set::VersionSet;
|
||||
|
||||
|
@ -32,16 +29,6 @@ struct OldestVersionsDependencyProvider<P: Package, VS: VersionSet>(
|
|||
impl<P: Package, VS: VersionSet> DependencyProvider<P, VS>
|
||||
for OldestVersionsDependencyProvider<P, VS>
|
||||
{
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
|
||||
&self,
|
||||
potential_packages: impl Iterator<Item = (T, U)>,
|
||||
) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
|
||||
Ok(choose_package_with_fewest_versions(
|
||||
|p| self.0.versions(p).into_iter().flatten().cloned(),
|
||||
potential_packages,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_dependencies(
|
||||
&self,
|
||||
p: &P,
|
||||
|
@ -49,6 +36,26 @@ impl<P: Package, VS: VersionSet> DependencyProvider<P, VS>
|
|||
) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
|
||||
self.0.get_dependencies(p, v)
|
||||
}
|
||||
|
||||
fn choose_version(
|
||||
&self,
|
||||
package: &P,
|
||||
range: &VS,
|
||||
) -> Result<Option<VS::V>, Box<dyn Error + Send + Sync>> {
|
||||
Ok(self
|
||||
.0
|
||||
.versions(package)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|&v| range.contains(v))
|
||||
.cloned())
|
||||
}
|
||||
|
||||
type Priority = <OfflineDependencyProvider<P, VS> as DependencyProvider<P, VS>>::Priority;
|
||||
|
||||
fn prioritize(&self, package: &P, range: &VS) -> Self::Priority {
|
||||
self.0.prioritize(package, range)
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as DP but it has a timeout.
|
||||
|
@ -74,13 +81,6 @@ impl<DP> TimeoutDependencyProvider<DP> {
|
|||
impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvider<P, VS>
|
||||
for TimeoutDependencyProvider<DP>
|
||||
{
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
|
||||
&self,
|
||||
potential_packages: impl Iterator<Item = (T, U)>,
|
||||
) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
|
||||
self.dp.choose_package_version(potential_packages)
|
||||
}
|
||||
|
||||
fn get_dependencies(
|
||||
&self,
|
||||
p: &P,
|
||||
|
@ -96,6 +96,20 @@ impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvid
|
|||
self.call_count.set(calls + 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn choose_version(
|
||||
&self,
|
||||
package: &P,
|
||||
range: &VS,
|
||||
) -> Result<Option<VS::V>, Box<dyn Error + Send + Sync>> {
|
||||
self.dp.choose_version(package, range)
|
||||
}
|
||||
|
||||
type Priority = DP::Priority;
|
||||
|
||||
fn prioritize(&self, package: &P, range: &VS) -> Self::Priority {
|
||||
self.dp.prioritize(package, range)
|
||||
}
|
||||
}
|
||||
|
||||
type NumVS = Range<NumberVersion>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue