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>>> =
|
let mut substitutions: FxHashMap<Rc<ConflictItem>, FxHashSet<Rc<ConflictItem>>> =
|
||||||
FxHashMap::default();
|
FxHashMap::default();
|
||||||
|
|
||||||
// Conflict sets that were directly defined in configuration.
|
// Track all existing conflict sets to avoid duplicates.
|
||||||
let mut direct_conflict_sets: FxHashSet<&ConflictSet> = FxHashSet::default();
|
let mut 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();
|
|
||||||
|
|
||||||
// Add groups in directly defined conflict sets to the graph.
|
// Add groups in directly defined conflict sets to the graph.
|
||||||
let mut seen: FxHashSet<&GroupName> = FxHashSet::default();
|
let mut seen: FxHashSet<&GroupName> = FxHashSet::default();
|
||||||
|
|
||||||
for set in &self.0 {
|
for set in &self.0 {
|
||||||
direct_conflict_sets.insert(set);
|
conflict_sets.insert(set.clone());
|
||||||
for item in set.iter() {
|
for item in set.iter() {
|
||||||
let ConflictKind::Group(group) = &item.kind else {
|
let ConflictKind::Group(group) = &item.kind else {
|
||||||
// TODO(john): Do we also want to handle extras here?
|
// TODO(john): Do we also want to handle extras here?
|
||||||
|
|
@ -183,28 +181,30 @@ impl Conflicts {
|
||||||
// at the end of each iteration.
|
// at the end of each iteration.
|
||||||
for (canonical_item, subs) in substitutions {
|
for (canonical_item, subs) in substitutions {
|
||||||
let mut new_conflict_sets = FxHashSet::default();
|
let mut new_conflict_sets = FxHashSet::default();
|
||||||
for conflict_set in direct_conflict_sets
|
for conflict_set in conflict_sets
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
|
||||||
.chain(transitive_conflict_sets.iter())
|
|
||||||
.filter(|set| set.contains_item(&canonical_item))
|
.filter(|set| set.contains_item(&canonical_item))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
{
|
{
|
||||||
for sub in &subs {
|
for sub in &subs {
|
||||||
let mut new_set = conflict_set
|
let new_set = conflict_set
|
||||||
.replaced_item(&canonical_item, (**sub).clone())
|
.replaced_item(&canonical_item, (**sub).clone())
|
||||||
.expect("`ConflictItem` should be in `ConflictSet`");
|
.expect("`ConflictItem` should be in `ConflictSet`");
|
||||||
if !direct_conflict_sets.contains(&new_set) {
|
if !conflict_sets.contains(&new_set) {
|
||||||
new_set = new_set.with_inferred_conflict();
|
new_conflict_sets.insert(new_set);
|
||||||
if !transitive_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)]
|
#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
|
||||||
pub struct ConflictSet {
|
pub struct ConflictSet {
|
||||||
set: BTreeSet<ConflictItem>,
|
set: BTreeSet<ConflictItem>,
|
||||||
is_inferred_conflict: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConflictSet {
|
impl ConflictSet {
|
||||||
|
|
@ -228,7 +227,6 @@ impl ConflictSet {
|
||||||
pub fn pair(item1: ConflictItem, item2: ConflictItem) -> Self {
|
pub fn pair(item1: ConflictItem, item2: ConflictItem) -> Self {
|
||||||
Self {
|
Self {
|
||||||
set: BTreeSet::from_iter(vec![item1, item2]),
|
set: BTreeSet::from_iter(vec![item1, item2]),
|
||||||
is_inferred_conflict: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,11 +253,6 @@ impl ConflictSet {
|
||||||
self.set.contains(conflict_item)
|
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.
|
/// Replace an old [`ConflictItem`] with a new one.
|
||||||
pub fn replaced_item(
|
pub fn replaced_item(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -272,17 +265,7 @@ impl ConflictSet {
|
||||||
}
|
}
|
||||||
new_set.remove(old);
|
new_set.remove(old);
|
||||||
new_set.insert(new);
|
new_set.insert(new);
|
||||||
Ok(Self {
|
Ok(Self { set: new_set })
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,7 +290,6 @@ impl TryFrom<Vec<ConflictItem>> for ConflictSet {
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
set: BTreeSet::from_iter(items),
|
set: BTreeSet::from_iter(items),
|
||||||
is_inferred_conflict: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -320,14 +320,9 @@ impl std::fmt::Display for ConflictError {
|
||||||
.iter()
|
.iter()
|
||||||
.all(|conflict| matches!(conflict.kind(), ConflictKind::Group(..)))
|
.all(|conflict| matches!(conflict.kind(), ConflictKind::Group(..)))
|
||||||
{
|
{
|
||||||
let conflict_source = if self.set.is_inferred_conflict() {
|
|
||||||
"transitively inferred"
|
|
||||||
} else {
|
|
||||||
"declared"
|
|
||||||
};
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Groups {} are incompatible with the {conflict_source} conflicts: {{{set}}}",
|
"Groups {} are incompatible with the conflicts: {{{set}}}",
|
||||||
conjunction(
|
conjunction(
|
||||||
self.conflicts
|
self.conflicts
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -2072,7 +2072,7 @@ fn group_basic() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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(())
|
Ok(())
|
||||||
|
|
@ -2217,7 +2217,7 @@ fn group_default() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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
|
// 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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
|
// 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 -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- 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.
|
// Disabling the default group should succeed.
|
||||||
|
|
|
||||||
|
|
@ -11038,7 +11038,7 @@ fn multiple_group_conflicts() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 3 packages in [TIME]
|
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(())
|
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"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
|
|
@ -11116,7 +11134,7 @@ fn transitive_group_conflicts_shallow() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
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"
|
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 -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
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(())
|
Ok(())
|
||||||
|
|
@ -11213,7 +11231,7 @@ fn transitive_group_conflicts_deep() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 7 packages in [TIME]
|
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"
|
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 -----
|
----- stderr -----
|
||||||
Resolved 7 packages in [TIME]
|
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(())
|
Ok(())
|
||||||
|
|
@ -11307,7 +11325,7 @@ fn transitive_group_conflicts_siblings() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
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"
|
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 -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
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(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue