Combine dependency clauses with the same root (#3225)

## Summary

Simplifies dependency errors of the form `you require package-a and you
require package-b` to `you require package-a and package-b`. Resolves
https://github.com/astral-sh/uv/issues/1009.
This commit is contained in:
Ibraheem Ahmed 2024-04-24 12:34:32 -04:00 committed by GitHub
parent 7ab0f64e17
commit 20e9589662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 108 additions and 36 deletions

View file

@ -202,15 +202,13 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
external2: &External<PubGrubPackage, Range<Version>>,
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
) -> String {
let external1 = self.format_external(external1);
let external2 = self.format_external(external2);
let external = self.format_both_external(external1, external2);
let terms = self.format_terms(current_terms);
format!(
"Because {}and {}we can conclude that {}",
Padded::from_string("", &external1, " "),
Padded::from_string("", &external2, ", "),
Padded::from_string("", &terms, ".")
"Because {}we can conclude that {}",
Padded::from_string("", &external, ", "),
Padded::from_string("", &terms, "."),
)
}
@ -305,13 +303,11 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
external: &External<PubGrubPackage, Range<Version>>,
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
) -> String {
let prior_external = self.format_external(prior_external);
let external = self.format_external(external);
let external = self.format_both_external(prior_external, external);
let terms = self.format_terms(current_terms);
format!(
"And because {}and {}we can conclude that {}",
Padded::from_string("", &prior_external, " "),
"And because {}we can conclude that {}",
Padded::from_string("", &external, ", "),
Padded::from_string("", &terms, "."),
)
@ -319,6 +315,59 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
}
impl PubGrubReportFormatter<'_> {
/// Format two external incompatibilities, combining them if possible.
fn format_both_external(
&self,
external1: &External<PubGrubPackage, Range<Version>>,
external2: &External<PubGrubPackage, Range<Version>>,
) -> String {
match (external1, external2) {
(
External::FromDependencyOf(package1, package_set1, dependency1, dependency_set1),
External::FromDependencyOf(package2, _, dependency2, dependency_set2),
) if package1 == package2 => {
let dependency_set1 = self.simplify_set(dependency_set1, dependency1);
let dependency1 = PackageRange::dependency(dependency1, &dependency_set1);
let dependency_set2 = self.simplify_set(dependency_set2, dependency2);
let dependency2 = PackageRange::dependency(dependency2, &dependency_set2);
match package1 {
PubGrubPackage::Root(Some(name)) => format!(
"{name} depends on {}and {}",
Padded::new("", &dependency1, " "),
dependency2,
),
PubGrubPackage::Root(None) => format!(
"you require {}and {}",
Padded::new("", &dependency1, " "),
dependency2,
),
_ => {
let package_set = self.simplify_set(package_set1, package1);
format!(
"{}",
PackageRange::compatibility(package1, &package_set)
.depends_on(dependency1.package, &dependency_set1)
.and(dependency2.package, &dependency_set2),
)
}
}
}
_ => {
let external1 = self.format_external(external1);
let external2 = self.format_external(external2);
format!(
"{}and {}",
Padded::from_string("", &external1, " "),
&external2,
)
}
}
}
/// Simplify a [`Range`] of versions using the available versions for a package.
fn simplify_set<'a>(
&self,
@ -806,34 +855,57 @@ impl PackageRange<'_> {
kind: PackageRangeKind::Available,
}
}
fn depends_on<'a>(
&'a self,
package: &'a PubGrubPackage,
range: &'a Range<Version>,
) -> DependsOn<'a> {
DependsOn {
first: self,
second: PackageRange::dependency(package, range),
package: self,
dependency1: PackageRange::dependency(package, range),
dependency2: None,
}
}
}
/// A representation of A depends on B.
/// A representation of A depends on B (and C).
#[derive(Debug)]
struct DependsOn<'a> {
first: &'a PackageRange<'a>,
second: PackageRange<'a>,
package: &'a PackageRange<'a>,
dependency1: PackageRange<'a>,
dependency2: Option<PackageRange<'a>>,
}
impl<'a> DependsOn<'a> {
/// Adds an additional dependency.
///
/// Note this overwrites previous calls to `DependsOn::and`.
fn and(mut self, package: &'a PubGrubPackage, range: &'a Range<Version>) -> DependsOn<'a> {
self.dependency2 = Some(PackageRange::dependency(package, range));
self
}
}
impl std::fmt::Display for DependsOn<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Padded::new("", self.first, " "))?;
if self.first.plural() {
write!(f, "{}", Padded::new("", self.package, " "))?;
if self.package.plural() {
write!(f, "depend on ")?;
} else {
write!(f, "depends on ")?;
};
write!(f, "{}", self.second)?;
match self.dependency2 {
Some(ref dependency2) => write!(
f,
"{}and{}",
Padded::new("", &self.dependency1, " "),
Padded::new(" ", &dependency2, "")
)?,
None => write!(f, "{}", self.dependency1)?,
}
Ok(())
}
}

View file

@ -2187,7 +2187,7 @@ dependencies = ["anyio==3.7.0", "anyio==4.0.0"]
----- stderr -----
× No solution found when resolving dependencies:
Because my-project depends on anyio==3.7.0 and my-project depends on anyio==4.0.0, we can conclude that the requirements are unsatisfiable.
Because my-project depends on anyio==3.7.0 and anyio==4.0.0, we can conclude that the requirements are unsatisfiable.
"###
);
@ -5573,7 +5573,7 @@ fn compile_constraints_incompatible_version() -> Result<()> {
----- stderr -----
× No solution found when resolving dependencies:
Because you require filelock==1.0.0 and you require filelock==3.8.0, we can conclude that the requirements are unsatisfiable.
Because you require filelock==1.0.0 and filelock==3.8.0, we can conclude that the requirements are unsatisfiable.
"###
);
@ -5601,7 +5601,7 @@ fn conflicting_url_markers() -> Result<()> {
----- stderr -----
× No solution found when resolving dependencies:
Because you require filelock==1.0.0 and you require filelock==3.8.0, we can conclude that the requirements are unsatisfiable.
Because you require filelock==1.0.0 and filelock==3.8.0, we can conclude that the requirements are unsatisfiable.
"###
);
@ -5749,7 +5749,7 @@ fn override_with_incompatible_constraint() -> Result<()> {
----- stderr -----
× No solution found when resolving dependencies:
Because you require anyio>=3.0.0 and you require anyio<3.0.0, we can conclude that the requirements are unsatisfiable.
Because you require anyio>=3.0.0 and anyio<3.0.0, we can conclude that the requirements are unsatisfiable.
"###
);

View file

@ -281,7 +281,7 @@ fn no_solution() {
----- stderr -----
× No solution found when resolving dependencies:
Because only flask<=3.0.2 is available and flask==3.0.2 depends on werkzeug>=3.0.0, we can conclude that flask>=3.0.2 depends on werkzeug>=3.0.0.
And because you require flask>=3.0.2 and you require werkzeug<1.0.0, we can conclude that the requirements are unsatisfiable.
And because you require flask>=3.0.2 and werkzeug<1.0.0, we can conclude that the requirements are unsatisfiable.
"###);
}

View file

@ -366,7 +366,7 @@ fn excluded_only_compatible_version() {
And because you require one of:
package-a<2.0.0
package-a>2.0.0
and you require package-b>=2.0.0,<3.0.0, we can conclude that the requirements are unsatisfiable.
and package-b>=2.0.0,<3.0.0, we can conclude that the requirements are unsatisfiable.
"###);
// Only `a==1.2.0` is available since `a==1.0.0` and `a==3.0.0` require
@ -475,7 +475,7 @@ fn dependency_excludes_range_of_compatible_versions() {
package-b<=1.0.0
package-b>=3.0.0
And because you require package-b>=2.0.0,<3.0.0 and you require package-c, we can conclude that the requirements are unsatisfiable.
And because you require package-b>=2.0.0,<3.0.0 and package-c, we can conclude that the requirements are unsatisfiable.
"###);
// Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0`
@ -600,7 +600,7 @@ fn dependency_excludes_non_contiguous_range_of_compatible_versions() {
package-b<=1.0.0
package-b>=3.0.0
And because you require package-b>=2.0.0,<3.0.0 and you require package-c, we can conclude that the requirements are unsatisfiable.
And because you require package-b>=2.0.0,<3.0.0 and package-c, we can conclude that the requirements are unsatisfiable.
"###);
// Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0`
@ -909,7 +909,7 @@ fn extra_incompatible_with_extra() {
× No solution found when resolving dependencies:
Because only package-a[extra-c]==1.0.0 is available and package-a[extra-c]==1.0.0 depends on package-b==2.0.0, we can conclude that all versions of package-a[extra-c] depend on package-b==2.0.0.
And because package-a[extra-b]==1.0.0 depends on package-b==1.0.0 and only package-a[extra-b]==1.0.0 is available, we can conclude that all versions of package-a[extra-b] and all versions of package-a[extra-c] are incompatible.
And because you require package-a[extra-b] and you require package-a[extra-c], we can conclude that the requirements are unsatisfiable.
And because you require package-a[extra-b] and package-a[extra-c], we can conclude that the requirements are unsatisfiable.
"###);
// Because both `extra_b` and `extra_c` are requested and they require incompatible
@ -1024,7 +1024,7 @@ fn extra_incompatible_with_root() {
----- stderr -----
× No solution found when resolving dependencies:
Because only package-a[extra]==1.0.0 is available and package-a[extra]==1.0.0 depends on package-b==1.0.0, we can conclude that all versions of package-a[extra] depend on package-b==1.0.0.
And because you require package-a[extra] and you require package-b==2.0.0, we can conclude that the requirements are unsatisfiable.
And because you require package-a[extra] and package-b==2.0.0, we can conclude that the requirements are unsatisfiable.
"###);
// Because the user requested `b==2.0.0` but the requested extra requires
@ -1130,7 +1130,7 @@ fn direct_incompatible_versions() {
----- stderr -----
× No solution found when resolving dependencies:
Because you require package-a==1.0.0 and you require package-a==2.0.0, we can conclude that the requirements are unsatisfiable.
Because you require package-a==1.0.0 and package-a==2.0.0, we can conclude that the requirements are unsatisfiable.
"###);
assert_not_installed(
@ -1184,7 +1184,7 @@ fn transitive_incompatible_with_root_version() {
----- stderr -----
× No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b==2.0.0 and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b==2.0.0.
And because you require package-a and you require package-b==1.0.0, we can conclude that the requirements are unsatisfiable.
And because you require package-a and package-b==1.0.0, we can conclude that the requirements are unsatisfiable.
"###);
assert_not_installed(
@ -1243,7 +1243,7 @@ fn transitive_incompatible_with_transitive() {
× No solution found when resolving dependencies:
Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-c==1.0.0, we can conclude that all versions of package-a depend on package-c==1.0.0.
And because package-b==1.0.0 depends on package-c==2.0.0 and only package-b==1.0.0 is available, we can conclude that all versions of package-a and all versions of package-b are incompatible.
And because you require package-a and you require package-b, we can conclude that the requirements are unsatisfiable.
And because you require package-a and package-b, we can conclude that the requirements are unsatisfiable.
"###);
assert_not_installed(
@ -1292,7 +1292,7 @@ fn transitive_incompatible_versions() {
----- stderr -----
× No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b==1.0.0 and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
Because package-a==1.0.0 depends on package-b==1.0.0 and package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable.
"###);
@ -1573,7 +1573,7 @@ fn local_transitive_greater_than() {
----- stderr -----
× No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b>2.0.0 and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b>2.0.0.
And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable.
And because you require package-a and package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable.
"###);
assert_not_installed(
@ -1684,7 +1684,7 @@ fn local_transitive_less_than() {
----- stderr -----
× No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b<2.0.0 and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b<2.0.0.
And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable.
And because you require package-a and package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable.
"###);
assert_not_installed(
@ -1843,7 +1843,7 @@ fn local_transitive_conflicting() {
----- stderr -----
× No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b==2.0.0+bar and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b==2.0.0+bar.
And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable.
And because you require package-a and package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable.
"###);
assert_not_installed(
@ -3379,7 +3379,7 @@ fn transitive_prerelease_and_stable_dependency_many_versions() {
Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-c>=2.0.0b1, we can conclude that all versions of package-a depend on package-c>=2.0.0b1.
And because only package-c<2.0.0b1 is available, we can conclude that all versions of package-a depend on package-c>3.0.0.
And because package-b==1.0.0 depends on package-c and only package-b==1.0.0 is available, we can conclude that all versions of package-b and all versions of package-a are incompatible.
And because you require package-a and you require package-b, we can conclude that the requirements are unsatisfiable.
And because you require package-a and package-b, we can conclude that the requirements are unsatisfiable.
hint: package-c was requested with a pre-release marker (e.g., package-c>=2.0.0b1), but pre-releases weren't enabled (try: `--prerelease=allow`)
"###);