use std::cmp::Reverse;
use hashbrown::hash_map::{EntryRef, OccupiedEntry};
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::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
use crate::{FxHashbrownMap, SentinelRange};
#[derive(Clone, Debug, Default)]
pub(crate) struct PubGrubPriorities {
package_priority: FxHashbrownMap<PackageName, PubGrubPriority>,
virtual_package_tiebreaker: FxHashbrownMap<PubGrubPackage, PubGrubTiebreaker>,
}
impl PubGrubPriorities {
pub(crate) fn insert(
&mut self,
package: &PubGrubPackage,
version: &Range<Version>,
urls: &ForkUrls,
) {
let len = self.virtual_package_tiebreaker.len();
self.virtual_package_tiebreaker
.entry_ref(package)
.or_insert_with(|| {
PubGrubTiebreaker::from(u32::try_from(len).expect("Less than 2**32 packages"))
});
let Some(name) = package.name_no_root() else {
return;
};
let len = self.package_priority.len();
match self.package_priority.entry_ref(name) {
EntryRef::Occupied(mut entry) => {
let index = Self::get_index(&entry).unwrap_or(len);
let priority = if urls.get(name).is_some() {
PubGrubPriority::DirectUrl(Reverse(index))
} else if version.as_singleton().is_some()
|| SentinelRange::from(version).is_sentinel()
{
PubGrubPriority::Singleton(Reverse(index))
} else {
if matches!(
entry.get(),
PubGrubPriority::ConflictEarly(_) | PubGrubPriority::ConflictLate(_)
) {
return;
}
PubGrubPriority::Unspecified(Reverse(index))
};
if priority > *entry.get() {
entry.insert(priority);
}
}
EntryRef::Vacant(entry) => {
let priority = if urls.get(name).is_some() {
PubGrubPriority::DirectUrl(Reverse(len))
} else if version.as_singleton().is_some()
|| SentinelRange::from(version).is_sentinel()
{
PubGrubPriority::Singleton(Reverse(len))
} else {
PubGrubPriority::Unspecified(Reverse(len))
};
entry.insert(priority);
}
}
}
fn get_index(
entry: &OccupiedEntry<'_, PackageName, PubGrubPriority, FxBuildHasher>,
) -> Option<usize> {
match entry.get() {
PubGrubPriority::ConflictLate(Reverse(index))
| PubGrubPriority::Unspecified(Reverse(index))
| PubGrubPriority::ConflictEarly(Reverse(index))
| PubGrubPriority::Singleton(Reverse(index))
| PubGrubPriority::DirectUrl(Reverse(index)) => Some(*index),
PubGrubPriority::Root => None,
}
}
pub(crate) fn get(
&self,
package: &PubGrubPackage,
) -> <UvDependencyProvider as DependencyProvider>::Priority {
match &**package {
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::System(_) => (PubGrubPriority::Root, PubGrubTiebreaker::from(3)),
PubGrubPackageInner::Marker { name, .. }
| PubGrubPackageInner::Extra { name, .. }
| PubGrubPackageInner::Group { name, .. }
| PubGrubPackageInner::Package { name, .. } => {
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!("Package not registered in prioritization: `{package:?}`")
} else {
PubGrubTiebreaker(Reverse(u32::MAX))
}
}
};
(package_priority, package_tiebreaker)
}
}
}
pub(crate) fn mark_conflict_early(&mut self, package: &PubGrubPackage) -> bool {
let Some(name) = package.name_no_root() else {
if cfg!(debug_assertions) {
panic!("URL packages must not be involved in conflict handling")
} else {
return false;
}
};
let len = self.package_priority.len();
match self.package_priority.entry_ref(name) {
EntryRef::Vacant(entry) => {
entry.insert(PubGrubPriority::ConflictEarly(Reverse(len)));
true
}
EntryRef::Occupied(mut entry) => {
if matches!(entry.get(), PubGrubPriority::ConflictEarly(_)) {
return false;
}
let index = Self::get_index(&entry).unwrap_or(len);
entry.insert(PubGrubPriority::ConflictEarly(Reverse(index)));
true
}
}
}
pub(crate) fn mark_conflict_late(&mut self, package: &PubGrubPackage) -> bool {
let Some(name) = package.name_no_root() else {
if cfg!(debug_assertions) {
panic!("URL packages must not be involved in conflict handling")
} else {
return false;
}
};
let len = self.package_priority.len();
match self.package_priority.entry_ref(name) {
EntryRef::Vacant(entry) => {
entry.insert(PubGrubPriority::ConflictLate(Reverse(len)));
true
}
EntryRef::Occupied(mut entry) => {
if matches!(
entry.get(),
PubGrubPriority::ConflictLate(_) | PubGrubPriority::ConflictEarly(_)
) {
return false;
}
let index = Self::get_index(&entry).unwrap_or(len);
entry.insert(PubGrubPriority::ConflictLate(Reverse(index)));
true
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum PubGrubPriority {
Unspecified(Reverse<usize>),
ConflictLate(Reverse<usize>),
ConflictEarly(Reverse<usize>),
Singleton(Reverse<usize>),
DirectUrl(Reverse<usize>),
Root,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct PubGrubTiebreaker(Reverse<u32>);
impl From<u32> for PubGrubTiebreaker {
fn from(value: u32) -> Self {
Self(Reverse(value))
}
}