From 415d8f1d8351607d54dba9e48e00217af4efaf14 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 17 Feb 2024 21:23:18 -0500 Subject: [PATCH] Avoid early error for transistive constraints --- .../uv-resolver/src/pubgrub/dependencies.rs | 41 ++++++------- crates/uv/tests/pip_compile.rs | 57 +++++++++++++++++++ 2 files changed, 74 insertions(+), 24 deletions(-) diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index 7bb6e083d52f..146f7e903503 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -1,9 +1,9 @@ use itertools::Itertools; -use pep440_rs::Version; use pubgrub::range::Range; use pubgrub::type_aliases::DependencyConstraints; use tracing::warn; +use pep440_rs::Version; use pep508_rs::{MarkerEnvironment, Requirement, VersionOrUrl}; use uv_normalize::{ExtraName, PackageName}; @@ -62,14 +62,24 @@ impl PubGrubDependencies { if let Some(entry) = dependencies.get_key_value(&package) { // Merge the versions. - let version = merge_versions(&package, entry.1, &version)?; + let intersection = entry.1.intersection(&version); + if intersection.is_empty() { + return Err(ResolveError::ConflictingVersions( + package.to_string(), + format!( + "`{package}{left}` does not intersect with `{package}{right}`", + left = entry.1, + right = version + ), + )); + } // Merge the package. if let Some(package) = merge_package(entry.0, &package)? { dependencies.remove(&package); - dependencies.insert(package, version); + dependencies.insert(package, intersection); } else { - dependencies.insert(package, version); + dependencies.insert(package, intersection); } } else { dependencies.insert(package.clone(), version.clone()); @@ -117,14 +127,14 @@ impl PubGrubDependencies { if let Some(entry) = dependencies.get_key_value(&package) { // Merge the versions. - let version = merge_versions(&package, entry.1, &version)?; + let intersection = entry.1.intersection(&version); // Merge the package. if let Some(package) = merge_package(entry.0, &package)? { dependencies.remove(&package); - dependencies.insert(package, version); + dependencies.insert(package, intersection); } else { - dependencies.insert(package, version); + dependencies.insert(package, intersection); } } } @@ -187,23 +197,6 @@ fn to_pubgrub( } } -/// Merge two [`Version`] ranges. -fn merge_versions( - package: &PubGrubPackage, - left: &Range, - right: &Range, -) -> Result, ResolveError> { - let result = left.intersection(right); - if result.is_empty() { - Err(ResolveError::ConflictingVersions( - package.to_string(), - format!("`{package}{left}` does not intersect with `{package}{right}`"), - )) - } else { - Ok(result) - } -} - /// Merge two [`PubGrubPackage`] instances. fn merge_package( left: &PubGrubPackage, diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 958da9082df1..68e93cd6104c 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -3540,3 +3540,60 @@ fn compile_constraints_incompatible_url() -> Result<()> { Ok(()) } + +/// Resolve a package from a `requirements.in` file, with a `constraints.txt` file pinning one of +/// its transitive dependencies to a specific version. +#[test] +fn compile_constraints_compatible_version() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("virtualenv")?; + + let constraints_txt = context.temp_dir.child("constraints.txt"); + constraints_txt.write_str("filelock==3.8.0")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--constraint") + .arg("constraints.txt"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because only anyio>=4 is available and you require anyio<4, we can + conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// Resolve a package from a `requirements.in` file, with a `constraints.txt` file pinning one of +/// its direct dependencies to an incompatible version. +#[test] +fn compile_constraints_incompatible_version() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("filelock==1.0.0")?; + + let constraints_txt = context.temp_dir.child("constraints.txt"); + constraints_txt.write_str("filelock==3.8.0")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--constraint") + .arg("constraints.txt"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ you require ∅ + "### + ); + + Ok(()) +}