mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-30 14:01:13 +00:00
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:
parent
7ab0f64e17
commit
20e9589662
4 changed files with 108 additions and 36 deletions
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
@ -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.
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
|
@ -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`)
|
||||
"###);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue