Skip to content

Commit 9cb49cb

Browse files
committed
Add incompatibility from proxy to base package
Attempt at #15199 by adding an incompatibility that lets pubgrub skip of marker packages when the base package already has an incompatible version. There are some false positives we still need to figure out, specifically around conflicts. The change looks perf neutral for the one case I tried: ``` $ hyperfine --warmup 2 "target/release/uv pip compile --universal scripts/requirements/airflow.in" "target/release/uv pip compile --universal scripts/requirements/airflow.in" Benchmark 1: target/release/uv pip compile --universal scripts/requirements/airflow.in Time (mean ± σ): 482.5 ms ± 12.8 ms [User: 602.7 ms, System: 205.2 ms] Range (min … max): 468.7 ms … 503.2 ms 10 runs Benchmark 2: target/release/uv pip compile --universal scripts/requirements/airflow.in Time (mean ± σ): 479.3 ms ± 21.0 ms [User: 604.0 ms, System: 200.5 ms] Range (min … max): 464.1 ms … 537.5 ms 10 runs Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options. Summary target/release/uv pip compile --universal scripts/requirements/airflow.in ran 1.01 ± 0.05 times faster than target/release/uv pip compile --universal scripts/requirements/airflow.in ```
1 parent 7d49571 commit 9cb49cb

File tree

6 files changed

+83
-65
lines changed

6 files changed

+83
-65
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ percent-encoding = { version = "2.3.1" }
141141
petgraph = { version = "0.8.0" }
142142
proc-macro2 = { version = "1.0.86" }
143143
procfs = { version = "0.17.0", default-features = false, features = ["flate2"] }
144-
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
144+
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "d8efd77673c9a90792da9da31b6c0da7ea8a324b" }
145145
quote = { version = "1.0.37" }
146146
rayon = { version = "1.10.0" }
147147
ref-cast = { version = "1.0.24" }
@@ -187,7 +187,7 @@ tracing-tree = { version = "0.4.0" }
187187
unicode-width = { version = "0.2.0" }
188188
unscanny = { version = "0.1.0" }
189189
url = { version = "2.5.2", features = ["serde"] }
190-
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
190+
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "d8efd77673c9a90792da9da31b6c0da7ea8a324b" }
191191
walkdir = { version = "2.5.0" }
192192
which = { version = "8.0.0", features = ["regex"] }
193193
windows = { version = "0.59.0", features = ["Win32_Globalization", "Win32_System_Console", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem"] }

crates/uv-resolver/src/pubgrub/priority.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ impl PubGrubPriorities {
151151
let package_tiebreaker = match self.virtual_package_tiebreaker.get(package) {
152152
Some(tiebreaker) => *tiebreaker,
153153
None => {
154-
if cfg!(debug_assertions) {
155-
panic!("Virtual package not known: `{package}`")
156-
} else {
157-
PubGrubTiebreaker(Reverse(u32::MAX))
158-
}
154+
//if cfg!(debug_assertions) {
155+
// panic!("Virtual package not known: `{package}`")
156+
//} else {
157+
PubGrubTiebreaker(Reverse(u32::MAX))
158+
//}
159159
}
160160
};
161161

crates/uv-resolver/src/resolver/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2878,6 +2878,36 @@ impl ForkState {
28782878
for_version: &Version,
28792879
dependencies: Vec<PubGrubDependency>,
28802880
) {
2881+
for dependency in &dependencies {
2882+
let PubGrubDependency {
2883+
package,
2884+
version,
2885+
parent: _,
2886+
url: _,
2887+
} = dependency;
2888+
2889+
let base_package = match &**package {
2890+
PubGrubPackageInner::Root(_)
2891+
| PubGrubPackageInner::Python(_)
2892+
| PubGrubPackageInner::System(_)
2893+
| PubGrubPackageInner::Package { .. } => continue,
2894+
PubGrubPackageInner::Dev { .. } => {
2895+
// The dependency groups of a package do not by themselves require the package
2896+
// itself.
2897+
continue;
2898+
}
2899+
PubGrubPackageInner::Extra { name, .. }
2900+
| PubGrubPackageInner::Marker { name, .. } => {
2901+
PubGrubPackage::from_package(name.clone(), None, None, MarkerTree::TRUE)
2902+
}
2903+
};
2904+
2905+
let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2906+
let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2907+
self.pubgrub
2908+
.add_proxy_package(proxy_package, base_package_id, version.clone());
2909+
}
2910+
28812911
let conflict = self.pubgrub.add_package_version_dependencies(
28822912
self.next,
28832913
for_version.clone(),

crates/uv/tests/it/lock.rs

Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13090,7 +13090,7 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()>
1309013090

1309113091
----- stderr -----
1309213092
× No solution found when resolving dependencies:
13093-
╰─▶ Because only datasets<=2.18.0 is available and your project depends on datasets>=2.19, we can conclude that your project's requirements are unsatisfiable.
13093+
╰─▶ Because your project depends on datasets<2.19 and datasets>=2.19, we can conclude that your project's requirements are unsatisfiable.
1309413094
");
1309513095

1309613096
Ok(())
@@ -26751,17 +26751,17 @@ fn lock_self_marker_incompatible() -> Result<()> {
2675126751
"#,
2675226752
)?;
2675326753

26754-
uv_snapshot!(context.filters(), context.lock(), @r###"
26754+
uv_snapshot!(context.filters(), context.lock(), @r"
2675526755
success: false
2675626756
exit_code: 1
2675726757
----- stdout -----
2675826758

2675926759
----- stderr -----
2676026760
× No solution found when resolving dependencies:
26761-
╰─▶ Because only project{sys_platform == 'win32'}<=0.1 is available and your project depends on itself at an incompatible version (project{sys_platform == 'win32'}>0.1), we can conclude that your project's requirements are unsatisfiable.
26761+
╰─▶ Because your project depends on itself at an incompatible version (project{sys_platform == 'win32'}>0.1), we can conclude that your project's requirements are unsatisfiable.
2676226762

2676326763
hint: The project `project` depends on itself at an incompatible version. This is likely a mistake. If you intended to depend on a third-party package named `project`, consider renaming the project `project` to avoid creating a conflict.
26764-
"###);
26764+
");
2676526765

2676626766
Ok(())
2676726767
}
@@ -29857,40 +29857,8 @@ fn lock_conflict_for_disjoint_python_version() -> Result<()> {
2985729857

2985829858
----- stderr -----
2985929859
× No solution found when resolving dependencies for split (markers: python_full_version >= '3.11'):
29860-
╰─▶ Because only the following versions of numpy{python_full_version >= '3.10'} are available:
29861-
numpy{python_full_version >= '3.10'}<=1.21.0
29862-
numpy{python_full_version >= '3.10'}==1.21.1
29863-
numpy{python_full_version >= '3.10'}==1.21.2
29864-
numpy{python_full_version >= '3.10'}==1.21.3
29865-
numpy{python_full_version >= '3.10'}==1.21.4
29866-
numpy{python_full_version >= '3.10'}==1.21.5
29867-
numpy{python_full_version >= '3.10'}==1.21.6
29868-
numpy{python_full_version >= '3.10'}==1.22.0
29869-
numpy{python_full_version >= '3.10'}==1.22.1
29870-
numpy{python_full_version >= '3.10'}==1.22.2
29871-
numpy{python_full_version >= '3.10'}==1.22.3
29872-
numpy{python_full_version >= '3.10'}==1.22.4
29873-
numpy{python_full_version >= '3.10'}==1.23.0
29874-
numpy{python_full_version >= '3.10'}==1.23.1
29875-
numpy{python_full_version >= '3.10'}==1.23.2
29876-
numpy{python_full_version >= '3.10'}==1.23.3
29877-
numpy{python_full_version >= '3.10'}==1.23.4
29878-
numpy{python_full_version >= '3.10'}==1.23.5
29879-
numpy{python_full_version >= '3.10'}==1.24.0
29880-
numpy{python_full_version >= '3.10'}==1.24.1
29881-
numpy{python_full_version >= '3.10'}==1.24.2
29882-
numpy{python_full_version >= '3.10'}==1.24.3
29883-
numpy{python_full_version >= '3.10'}==1.24.4
29884-
numpy{python_full_version >= '3.10'}==1.25.0
29885-
numpy{python_full_version >= '3.10'}==1.25.1
29886-
numpy{python_full_version >= '3.10'}==1.25.2
29887-
numpy{python_full_version >= '3.10'}==1.26.0
29888-
numpy{python_full_version >= '3.10'}==1.26.1
29889-
numpy{python_full_version >= '3.10'}==1.26.2
29890-
numpy{python_full_version >= '3.10'}==1.26.3
29891-
numpy{python_full_version >= '3.10'}==1.26.4
29892-
and pandas==1.5.3 depends on numpy{python_full_version >= '3.10'}>=1.21.0, we can conclude that pandas==1.5.3 depends on numpy>=1.21.0.
29893-
And because your project depends on numpy==1.20.3 and pandas==1.5.3, we can conclude that your project's requirements are unsatisfiable.
29860+
╰─▶ Because pandas==1.5.3 depends on numpy{python_full_version >= '3.10'}>=1.21.0 and your project depends on numpy==1.20.3, we can conclude that your project and pandas==1.5.3 are incompatible.
29861+
And because your project depends on pandas==1.5.3, we can conclude that your project's requirements are unsatisfiable.
2989429862

2989529863
hint: While the active Python version is 3.9, the resolution failed for other Python versions supported by your project. Consider limiting your project's supported Python versions using `requires-python`.
2989629864
");
@@ -30111,18 +30079,7 @@ fn lock_conflict_for_disjoint_platform() -> Result<()> {
3011130079

3011230080
----- stderr -----
3011330081
× No solution found when resolving dependencies for split (markers: sys_platform == 'exotic'):
30114-
╰─▶ Because only the following versions of numpy{sys_platform == 'exotic'} are available:
30115-
numpy{sys_platform == 'exotic'}<=1.24.0
30116-
numpy{sys_platform == 'exotic'}==1.24.1
30117-
numpy{sys_platform == 'exotic'}==1.24.2
30118-
numpy{sys_platform == 'exotic'}==1.24.3
30119-
numpy{sys_platform == 'exotic'}==1.24.4
30120-
numpy{sys_platform == 'exotic'}==1.25.0
30121-
numpy{sys_platform == 'exotic'}==1.25.1
30122-
numpy{sys_platform == 'exotic'}==1.25.2
30123-
numpy{sys_platform == 'exotic'}>1.26
30124-
and your project depends on numpy{sys_platform == 'exotic'}>=1.24,<1.26, we can conclude that your project depends on numpy>=1.24.0,<=1.25.2.
30125-
And because your project depends on numpy>=1.26, we can conclude that your project's requirements are unsatisfiable.
30082+
╰─▶ Because your project depends on numpy{sys_platform == 'exotic'}>=1.24,<1.26 and numpy>=1.26, we can conclude that your project's requirements are unsatisfiable.
3012630083

3012730084
hint: The resolution failed for an environment that is not the current one, consider limiting the environments with `tool.uv.environments`.
3012830085
");
@@ -31592,3 +31549,38 @@ fn lock_required_intersection() -> Result<()> {
3159231549

3159331550
Ok(())
3159431551
}
31552+
31553+
/// Ensure conflicts on virtual packages (such as markers) give good error messages.
31554+
#[test]
31555+
fn collapsed_error_with_marker_packages() -> Result<()> {
31556+
let context = TestContext::new("3.12");
31557+
31558+
let pyproject_toml = indoc! {r#"
31559+
[project]
31560+
name = "test-project"
31561+
version = "1.0.0"
31562+
requires-python = ">=3.12"
31563+
dependencies = [
31564+
"anyio<=4.3.0; sys_platform == 'other'",
31565+
"anyio>=4.4.0; python_version < '3.14'",
31566+
]
31567+
"#};
31568+
context
31569+
.temp_dir
31570+
.child("pyproject.toml")
31571+
.write_str(pyproject_toml)?;
31572+
31573+
uv_snapshot!(context.filters(), context.lock(), @r"
31574+
success: false
31575+
exit_code: 1
31576+
----- stdout -----
31577+
31578+
----- stderr -----
31579+
× No solution found when resolving dependencies for split (markers: python_full_version < '3.14' and sys_platform == 'other'):
31580+
╰─▶ Because your project depends on anyio{sys_platform == 'other'} and anyio{python_full_version < '3.14'}>=4.4.0, we can conclude that your project's requirements are unsatisfiable.
31581+
31582+
hint: The resolution failed for an environment that is not the current one, consider limiting the environments with `tool.uv.environments`.
31583+
");
31584+
31585+
Ok(())
31586+
}

crates/uv/tests/it/lock_scenarios.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3260,7 +3260,7 @@ fn fork_non_local_fork_marker_direct() -> Result<()> {
32603260
32613261
----- stderr -----
32623262
× No solution found when resolving dependencies:
3263-
╰─▶ Because package-a==1.0.0 depends on package-c<2.0.0 and package-b==1.0.0 depends on package-c>=2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible.
3263+
╰─▶ Because package-a==1.0.0 depends on package-c<2.0.0 and package-b==1.0.0 depends on package-c>=2.0.0, we can conclude that package-b==1.0.0 and package-a{sys_platform == 'linux'}==1.0.0 are incompatible.
32643264
And because your project depends on package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0, we can conclude that your project's requirements are unsatisfiable.
32653265
"
32663266
);
@@ -3336,11 +3336,7 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> {
33363336
33373337
----- stderr -----
33383338
× No solution found when resolving dependencies:
3339-
╰─▶ Because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and only the following versions of package-c{sys_platform == 'linux'} are available:
3340-
package-c{sys_platform == 'linux'}==1.0.0
3341-
package-c{sys_platform == 'linux'}>2.0.0
3342-
we can conclude that package-a==1.0.0 depends on package-c{sys_platform == 'linux'}==1.0.0.
3343-
And because only package-c{sys_platform == 'darwin'}<=2.0.0 is available and package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible.
3339+
╰─▶ Because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible.
33443340
And because your project depends on package-a==1.0.0 and package-b==1.0.0, we can conclude that your project's requirements are unsatisfiable.
33453341
"
33463342
);

0 commit comments

Comments
 (0)