mirror of https://github.com/astral-sh/uv
Remove tracking of inferred dependency conflicts (#15909)
Alternative to #15884 (see commentary there) Closes https://github.com/astral-sh/uv/issues/15869
This commit is contained in:
parent
ab2f394019
commit
8b86bd530e
|
|
@ -95,16 +95,14 @@ impl Conflicts {
|
|||
let mut substitutions: FxHashMap<Rc<ConflictItem>, FxHashSet<Rc<ConflictItem>>> =
|
||||
FxHashMap::default();
|
||||
|
||||
// Conflict sets that were directly defined in configuration.
|
||||
let mut direct_conflict_sets: FxHashSet<&ConflictSet> = FxHashSet::default();
|
||||
// Conflict sets that we will transitively infer in this method.
|
||||
let mut transitive_conflict_sets: FxHashSet<ConflictSet> = FxHashSet::default();
|
||||
// Track all existing conflict sets to avoid duplicates.
|
||||
let mut conflict_sets: FxHashSet<ConflictSet> = FxHashSet::default();
|
||||
|
||||
// Add groups in directly defined conflict sets to the graph.
|
||||
let mut seen: FxHashSet<&GroupName> = FxHashSet::default();
|
||||
|
||||
for set in &self.0 {
|
||||
direct_conflict_sets.insert(set);
|
||||
conflict_sets.insert(set.clone());
|
||||
for item in set.iter() {
|
||||
let ConflictKind::Group(group) = &item.kind else {
|
||||
// TODO(john): Do we also want to handle extras here?
|
||||
|
|
@ -183,28 +181,30 @@ impl Conflicts {
|
|||
// at the end of each iteration.
|
||||
for (canonical_item, subs) in substitutions {
|
||||
let mut new_conflict_sets = FxHashSet::default();
|
||||
for conflict_set in direct_conflict_sets
|
||||
for conflict_set in conflict_sets
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(transitive_conflict_sets.iter())
|
||||
.filter(|set| set.contains_item(&canonical_item))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
for sub in &subs {
|
||||
let mut new_set = conflict_set
|
||||
let new_set = conflict_set
|
||||
.replaced_item(&canonical_item, (**sub).clone())
|
||||
.expect("`ConflictItem` should be in `ConflictSet`");
|
||||
if !direct_conflict_sets.contains(&new_set) {
|
||||
new_set = new_set.with_inferred_conflict();
|
||||
if !transitive_conflict_sets.contains(&new_set) {
|
||||
new_conflict_sets.insert(new_set);
|
||||
}
|
||||
if !conflict_sets.contains(&new_set) {
|
||||
new_conflict_sets.insert(new_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
transitive_conflict_sets.extend(new_conflict_sets.into_iter());
|
||||
conflict_sets.extend(new_conflict_sets.into_iter());
|
||||
}
|
||||
|
||||
self.0.extend(transitive_conflict_sets);
|
||||
// Add all newly discovered conflict sets (excluding the originals already in self.0)
|
||||
for set in conflict_sets {
|
||||
if !self.0.contains(&set) {
|
||||
self.0.push(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +220,6 @@ impl Conflicts {
|
|||
#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ConflictSet {
|
||||
set: BTreeSet<ConflictItem>,
|
||||
is_inferred_conflict: bool,
|
||||
}
|
||||
|
||||
impl ConflictSet {
|
||||
|
|
@ -228,7 +227,6 @@ impl ConflictSet {
|
|||
pub fn pair(item1: ConflictItem, item2: ConflictItem) -> Self {
|
||||
Self {
|
||||
set: BTreeSet::from_iter(vec![item1, item2]),
|
||||
is_inferred_conflict: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,11 +253,6 @@ impl ConflictSet {
|
|||
self.set.contains(conflict_item)
|
||||
}
|
||||
|
||||
/// This [`ConflictSet`] was inferred from directly defined conflicts.
|
||||
pub fn is_inferred_conflict(&self) -> bool {
|
||||
self.is_inferred_conflict
|
||||
}
|
||||
|
||||
/// Replace an old [`ConflictItem`] with a new one.
|
||||
pub fn replaced_item(
|
||||
&self,
|
||||
|
|
@ -272,17 +265,7 @@ impl ConflictSet {
|
|||
}
|
||||
new_set.remove(old);
|
||||
new_set.insert(new);
|
||||
Ok(Self {
|
||||
set: new_set,
|
||||
is_inferred_conflict: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Mark this [`ConflictSet`] as being inferred from directly
|
||||
/// defined conflicts.
|
||||
fn with_inferred_conflict(mut self) -> Self {
|
||||
self.is_inferred_conflict = true;
|
||||
self
|
||||
Ok(Self { set: new_set })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +290,6 @@ impl TryFrom<Vec<ConflictItem>> for ConflictSet {
|
|||
}
|
||||
Ok(Self {
|
||||
set: BTreeSet::from_iter(items),
|
||||
is_inferred_conflict: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,14 +320,9 @@ impl std::fmt::Display for ConflictError {
|
|||
.iter()
|
||||
.all(|conflict| matches!(conflict.kind(), ConflictKind::Group(..)))
|
||||
{
|
||||
let conflict_source = if self.set.is_inferred_conflict() {
|
||||
"transitively inferred"
|
||||
} else {
|
||||
"declared"
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"Groups {} are incompatible with the {conflict_source} conflicts: {{{set}}}",
|
||||
"Groups {} are incompatible with the conflicts: {{{set}}}",
|
||||
conjunction(
|
||||
self.conflicts
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -2072,7 +2072,7 @@ fn group_basic() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Groups `group1` and `group2` are incompatible with the declared conflicts: {`project:group1`, `project:group2`}
|
||||
error: Groups `group1` and `group2` are incompatible with the conflicts: {`project:group1`, `project:group2`}
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -2217,7 +2217,7 @@ fn group_default() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Groups `group1` (enabled by default) and `group2` are incompatible with the declared conflicts: {`project:group1`, `project:group2`}
|
||||
error: Groups `group1` (enabled by default) and `group2` are incompatible with the conflicts: {`project:group1`, `project:group2`}
|
||||
"###);
|
||||
|
||||
// If the group is explicitly requested, we should still fail, but shouldn't mark it as
|
||||
|
|
@ -2228,7 +2228,7 @@ fn group_default() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Groups `group1` and `group2` are incompatible with the declared conflicts: {`project:group1`, `project:group2`}
|
||||
error: Groups `group1` and `group2` are incompatible with the conflicts: {`project:group1`, `project:group2`}
|
||||
"###);
|
||||
|
||||
// If we install via `--all-groups`, we should also avoid marking the group as "enabled by
|
||||
|
|
@ -2239,7 +2239,7 @@ fn group_default() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Groups `group1` and `group2` are incompatible with the declared conflicts: {`project:group1`, `project:group2`}
|
||||
error: Groups `group1` and `group2` are incompatible with the conflicts: {`project:group1`, `project:group2`}
|
||||
"###);
|
||||
|
||||
// Disabling the default group should succeed.
|
||||
|
|
|
|||
|
|
@ -11038,7 +11038,7 @@ fn multiple_group_conflicts() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
error: Groups `bar` and `foo` are incompatible with the declared conflicts: {`project:bar`, `project:foo`}
|
||||
error: Groups `bar` and `foo` are incompatible with the conflicts: {`project:bar`, `project:foo`}
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
|
@ -11075,6 +11075,24 @@ fn transitive_group_conflicts_shallow() -> Result<()> {
|
|||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--check"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
|
|
@ -11116,7 +11134,7 @@ fn transitive_group_conflicts_shallow() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
error: Groups `magic` and `test` are incompatible with the declared conflicts: {`example:magic`, `example:test`}
|
||||
error: Groups `magic` and `test` are incompatible with the conflicts: {`example:magic`, `example:test`}
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev").arg("--group").arg("magic"), @r"
|
||||
|
|
@ -11126,7 +11144,7 @@ fn transitive_group_conflicts_shallow() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
error: Groups `dev` and `magic` are incompatible with the transitively inferred conflicts: {`example:dev`, `example:magic`}
|
||||
error: Groups `dev` and `magic` are incompatible with the conflicts: {`example:dev`, `example:magic`}
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
|
@ -11213,7 +11231,7 @@ fn transitive_group_conflicts_deep() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 7 packages in [TIME]
|
||||
error: Groups `dev` and `magic` are incompatible with the transitively inferred conflicts: {`example:dev`, `example:magic`}
|
||||
error: Groups `dev` and `magic` are incompatible with the conflicts: {`example:dev`, `example:magic`}
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-dev").arg("--group").arg("intermediate").arg("--group").arg("magic"), @r"
|
||||
|
|
@ -11223,7 +11241,7 @@ fn transitive_group_conflicts_deep() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 7 packages in [TIME]
|
||||
error: Groups `intermediate` and `magic` are incompatible with the transitively inferred conflicts: {`example:intermediate`, `example:magic`}
|
||||
error: Groups `intermediate` and `magic` are incompatible with the conflicts: {`example:intermediate`, `example:magic`}
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
|
@ -11307,7 +11325,7 @@ fn transitive_group_conflicts_siblings() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
error: Groups `dev` (enabled by default) and `dev2` are incompatible with the transitively inferred conflicts: {`example:dev`, `example:dev2`}
|
||||
error: Groups `dev` (enabled by default) and `dev2` are incompatible with the conflicts: {`example:dev`, `example:dev2`}
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev").arg("--group").arg("dev2"), @r"
|
||||
|
|
@ -11317,7 +11335,7 @@ fn transitive_group_conflicts_siblings() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
error: Groups `dev` and `dev2` are incompatible with the transitively inferred conflicts: {`example:dev`, `example:dev2`}
|
||||
error: Groups `dev` and `dev2` are incompatible with the conflicts: {`example:dev`, `example:dev2`}
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Reference in New Issue