uv-pypi-types: make room for group names in addition to extras

This adds support for providing conflicting group names in addition to
extra names to `Conflicts`.

This merely makes "room" for it in the types while keeping everything
working. We'll add proper support for it in the next commit.

Note that one interesting trick we do here is depend directly on
`hashbrown` so that we can make use of its `Equivalent` trait. This in
turn lets us use things like `ConflictItemRef` as a lookup key for a
hashset that contains `ConflictItem`. This mirrors using a `&str` as a
lookup key for a hashset that contains `String`, but works for arbitrary
types. `std` doesn't support this, but `hashbrown` does. This trick in
turn lets us simplify some of our data structures.

This also rejiggers some of the serde-interaction with the conflicting
types. We now use a wire type to represent our conflicting items for
more flexibility. i.e., Support `extra` XOR `group` fields.
This commit is contained in:
Andrew Gallant 2024-11-13 12:47:38 -05:00 committed by Andrew Gallant
parent cda8b3276a
commit 06943ca870
11 changed files with 315 additions and 63 deletions

View file

@ -43,6 +43,7 @@ clap = { workspace = true, features = ["derive"], optional = true }
dashmap = { workspace = true }
either = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true, features = ["serde"] }

View file

@ -25,6 +25,13 @@ pub use resolver::{
pub use version_map::VersionMap;
pub use yanks::AllowedYanks;
/// A custom `HashSet` using `hashbrown`.
///
/// We use `hashbrown` instead of `std` to get access to its `Equivalent`
/// trait. This lets use store things like `ConflictItem`, but refer to it via
/// `ConflictItemRef`. i.e., We can avoid allocs on lookups.
type FxHashbrownSet<T> = hashbrown::HashSet<T, rustc_hash::FxBuildHasher>;
mod bare;
mod candidate_selector;

View file

@ -40,8 +40,8 @@ use uv_pep440::Version;
use uv_pep508::{split_scheme, MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError};
use uv_platform_tags::{TagCompatibility, TagPriority, Tags};
use uv_pypi_types::{
redact_credentials, Conflicts, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
RequirementSource,
redact_credentials, ConflictPackage, Conflicts, HashDigest, ParsedArchiveUrl, ParsedGitUrl,
Requirement, RequirementSource,
};
use uv_types::{BuildContext, HashStrategy};
use uv_workspace::dependency_groups::DependencyGroupError;
@ -634,11 +634,18 @@ impl Lock {
if !self.conflicts.is_empty() {
let mut list = Array::new();
for groups in self.conflicts.iter() {
list.push(each_element_on_its_line_array(groups.iter().map(|group| {
for set in self.conflicts.iter() {
list.push(each_element_on_its_line_array(set.iter().map(|item| {
let mut table = InlineTable::new();
table.insert("package", Value::from(group.package().to_string()));
table.insert("extra", Value::from(group.extra().to_string()));
table.insert("package", Value::from(item.package().to_string()));
match item.conflict() {
ConflictPackage::Extra(ref extra) => {
table.insert("extra", Value::from(extra.to_string()));
}
ConflictPackage::Group(ref group) => {
table.insert("group", Value::from(group.to_string()));
}
}
table
})));
}

View file

@ -1,7 +1,5 @@
use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet};
use uv_normalize::{ExtraName, PackageName};
use uv_pep508::{MarkerEnvironment, MarkerTree};
use uv_pypi_types::{ConflictItem, ConflictItemRef, ResolverMarkerEnvironment};
@ -97,7 +95,7 @@ enum Kind {
/// The markers associated with this resolver fork.
markers: MarkerTree,
/// Conflicting group exclusions.
exclude: Arc<FxHashMap<PackageName, FxHashSet<ExtraName>>>,
exclude: Arc<crate::FxHashbrownSet<ConflictItem>>,
},
}
@ -135,7 +133,7 @@ impl ResolverEnvironment {
let kind = Kind::Universal {
initial_forks: initial_forks.into(),
markers: MarkerTree::TRUE,
exclude: Arc::new(FxHashMap::default()),
exclude: Arc::new(crate::FxHashbrownSet::default()),
};
ResolverEnvironment { kind }
}
@ -166,10 +164,7 @@ impl ResolverEnvironment {
pub(crate) fn included_by_group(&self, group: ConflictItemRef<'_>) -> bool {
match self.kind {
Kind::Specific { .. } => true,
Kind::Universal { ref exclude, .. } => !exclude
.get(group.package())
.map(|set| set.contains(group.extra()))
.unwrap_or(false),
Kind::Universal { ref exclude, .. } => !exclude.contains(&group),
}
}
@ -227,7 +222,7 @@ impl ResolverEnvironment {
/// specific marker environment. i.e., "pip"-style resolution.
pub(crate) fn exclude_by_group(
&self,
groups: impl IntoIterator<Item = ConflictItem>,
items: impl IntoIterator<Item = ConflictItem>,
) -> ResolverEnvironment {
match self.kind {
Kind::Specific { .. } => {
@ -238,12 +233,9 @@ impl ResolverEnvironment {
ref markers,
ref exclude,
} => {
let mut exclude: FxHashMap<_, _> = (**exclude).clone();
for group in groups {
exclude
.entry(group.package().clone())
.or_default()
.insert(group.extra().clone());
let mut exclude: crate::FxHashbrownSet<_> = (**exclude).clone();
for item in items {
exclude.insert(item);
}
let kind = Kind::Universal {
initial_forks: Arc::clone(initial_forks),

View file

@ -2950,7 +2950,7 @@ struct Fork {
/// This exists to make some access patterns more efficient. Namely,
/// it makes it easy to check whether there's a dependency with a
/// particular conflicting group in this fork.
conflicts: FxHashMap<PackageName, FxHashSet<ExtraName>>,
conflicts: crate::FxHashbrownSet<ConflictItem>,
/// The resolver environment for this fork.
///
/// Principally, this corresponds to the markers in this for. So in the
@ -2971,7 +2971,7 @@ impl Fork {
fn new(env: ResolverEnvironment) -> Fork {
Fork {
dependencies: vec![],
conflicts: FxHashMap::default(),
conflicts: crate::FxHashbrownSet::default(),
env,
}
}
@ -2979,10 +2979,7 @@ impl Fork {
/// Add a dependency to this fork.
fn add_dependency(&mut self, dep: PubGrubDependency) {
if let Some(conflicting_item) = dep.package.conflicting_item() {
self.conflicts
.entry(conflicting_item.package().clone())
.or_default()
.insert(conflicting_item.extra().clone());
self.conflicts.insert(conflicting_item.to_owned());
}
self.dependencies.push(dep);
}
@ -3001,9 +2998,7 @@ impl Fork {
return true;
}
if let Some(conflicting_item) = dep.package.conflicting_item() {
if let Some(set) = self.conflicts.get_mut(conflicting_item.package()) {
set.remove(conflicting_item.extra());
}
self.conflicts.remove(&conflicting_item);
}
false
});
@ -3011,11 +3006,8 @@ impl Fork {
/// Returns true if any of the dependencies in this fork contain a
/// dependency with the given package and extra values.
fn contains_conflicting_item(&self, group: ConflictItemRef<'_>) -> bool {
self.conflicts
.get(group.package())
.map(|set| set.contains(group.extra()))
.unwrap_or(false)
fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
self.conflicts.contains(&item)
}
/// Exclude the given groups from this fork.
@ -3031,9 +3023,7 @@ impl Fork {
return true;
}
if let Some(conflicting_item) = dep.package.conflicting_item() {
if let Some(set) = self.conflicts.get_mut(conflicting_item.package()) {
set.remove(conflicting_item.extra());
}
self.conflicts.remove(&conflicting_item);
}
false
});