diff --git a/crates/uv-resolver/src/dependency_provider.rs b/crates/uv-resolver/src/dependency_provider.rs index dfdec077cd9e5..54c1664d2e7a1 100644 --- a/crates/uv-resolver/src/dependency_provider.rs +++ b/crates/uv-resolver/src/dependency_provider.rs @@ -18,7 +18,7 @@ impl DependencyProvider for UvDependencyProvider { type VS = Range; type M = UnavailableReason; /// Main priority and tiebreak for virtual packages. - type Priority = (Option, Option); + type Priority = (PubGrubPriority, PubGrubTiebreaker); type Err = Infallible; fn prioritize( @@ -46,3 +46,16 @@ impl DependencyProvider for UvDependencyProvider { unimplemented!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn priority_size() { + assert_eq!( + size_of::<::Priority>(), + 24 + ); + } +} diff --git a/crates/uv-resolver/src/pubgrub/priority.rs b/crates/uv-resolver/src/pubgrub/priority.rs index 32da9b0a26a2a..539d2c51afef0 100644 --- a/crates/uv-resolver/src/pubgrub/priority.rs +++ b/crates/uv-resolver/src/pubgrub/priority.rs @@ -1,15 +1,15 @@ use std::cmp::Reverse; use hashbrown::hash_map::{EntryRef, OccupiedEntry}; -use pubgrub::Range; +use pubgrub::{DependencyProvider, Range}; use rustc_hash::FxBuildHasher; use uv_normalize::PackageName; use uv_pep440::Version; +use crate::dependency_provider::UvDependencyProvider; use crate::fork_urls::ForkUrls; -use crate::pubgrub::package::PubGrubPackage; -use crate::pubgrub::PubGrubPackageInner; +use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; use crate::{FxHashbrownMap, SentinelRange}; /// A prioritization map to guide the PubGrub resolution process. @@ -21,6 +21,10 @@ use crate::{FxHashbrownMap, SentinelRange}; /// version over packages that are constrained in some way over packages that are unconstrained. /// /// See: +/// +/// Our main priority is the package name, the earlier we encounter a package, the higher its +/// priority. This way, all virtual packages of the same name will be applied in a batch. To ensure +/// determinism, we also track the discovery order of virtual packages as secondary order. #[derive(Clone, Debug, Default)] pub(crate) struct PubGrubPriorities { package_priority: FxHashbrownMap, @@ -113,17 +117,50 @@ impl PubGrubPriorities { pub(crate) fn get( &self, package: &PubGrubPackage, - ) -> (Option, Option) { - let package_priority = match &**package { - PubGrubPackageInner::Root(_) => Some(PubGrubPriority::Root), - PubGrubPackageInner::Python(_) => Some(PubGrubPriority::Root), - PubGrubPackageInner::Marker { name, .. } => self.package_priority.get(name).copied(), - PubGrubPackageInner::Extra { name, .. } => self.package_priority.get(name).copied(), - PubGrubPackageInner::Dev { name, .. } => self.package_priority.get(name).copied(), - PubGrubPackageInner::Package { name, .. } => self.package_priority.get(name).copied(), - }; - let package_tiebreaker = self.virtual_package_tiebreaker.get(package).copied(); - (package_priority, package_tiebreaker) + ) -> ::Priority { + match &**package { + // There is only a single root package despite the value. The priorities on root don't + // matter for the resolution output, since the Pythons don't have dependencies + // themselves and are only used when the package is incompatible. + PubGrubPackageInner::Root(_) => (PubGrubPriority::Root, PubGrubTiebreaker::from(0)), + PubGrubPackageInner::Python(PubGrubPython::Installed) => { + (PubGrubPriority::Root, PubGrubTiebreaker::from(1)) + } + PubGrubPackageInner::Python(PubGrubPython::Target) => { + (PubGrubPriority::Root, PubGrubTiebreaker::from(2)) + } + PubGrubPackageInner::Marker { name, .. } + | PubGrubPackageInner::Extra { name, .. } + | PubGrubPackageInner::Dev { name, .. } + | PubGrubPackageInner::Package { name, .. } => { + // To ensure deterministic resolution, each (virtual) package needs to be registered + // on discovery (as dependency of another package), before we query it for + // prioritization. + let package_priority = match self.package_priority.get(name) { + Some(priority) => *priority, + None => { + if cfg!(debug_assertions) { + panic!("Package not known: `{name}` from `{package}`") + } else { + PubGrubPriority::Unspecified(Reverse(usize::MAX)) + } + } + }; + + let package_tiebreaker = match self.virtual_package_tiebreaker.get(package) { + Some(tiebreaker) => *tiebreaker, + None => { + if cfg!(debug_assertions) { + panic!("Virtual package not known: `{package}`") + } else { + PubGrubTiebreaker(Reverse(u32::MAX)) + } + } + }; + + (package_priority, package_tiebreaker) + } + } } /// Mark a package as prioritized by setting it to [`PubGrubPriority::ConflictEarly`], if it