Skip to content

Commit 8eba22d

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 ed499d7 commit 8eba22d

File tree

6 files changed

+80
-65
lines changed

6 files changed

+80
-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
@@ -138,7 +138,7 @@ percent-encoding = { version = "2.3.1" }
138138
petgraph = { version = "0.8.0" }
139139
proc-macro2 = { version = "1.0.86" }
140140
procfs = { version = "0.17.0", default-features = false, features = ["flate2"] }
141-
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
141+
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "d8efd77673c9a90792da9da31b6c0da7ea8a324b" }
142142
quote = { version = "1.0.37" }
143143
rayon = { version = "1.10.0" }
144144
ref-cast = { version = "1.0.24" }
@@ -184,7 +184,7 @@ tracing-tree = { version = "0.4.0" }
184184
unicode-width = { version = "0.2.0" }
185185
unscanny = { version = "0.1.0" }
186186
url = { version = "2.5.2", features = ["serde"] }
187-
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
187+
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "d8efd77673c9a90792da9da31b6c0da7ea8a324b" }
188188
walkdir = { version = "2.5.0" }
189189
which = { version = "8.0.0", features = ["regex"] }
190190
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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,6 +2870,35 @@ impl ForkState {
28702870
for_version: &Version,
28712871
dependencies: Vec<PubGrubDependency>,
28722872
) {
2873+
for dependency in &dependencies {
2874+
let PubGrubDependency {
2875+
package,
2876+
version,
2877+
parent: _,
2878+
url: _,
2879+
} = dependency;
2880+
2881+
let base_package = match &**package {
2882+
PubGrubPackageInner::Root(_)
2883+
| PubGrubPackageInner::Python(_)
2884+
| PubGrubPackageInner::System(_)
2885+
| PubGrubPackageInner::Package { .. } => continue,
2886+
PubGrubPackageInner::Extra { .. } | PubGrubPackageInner::Dev { .. } => {
2887+
// TODO(konsti): This incompatibility should still work when using
2888+
// tool.uv.conflicts, why does it give false positives?
2889+
continue;
2890+
}
2891+
PubGrubPackageInner::Marker { name, .. } => {
2892+
PubGrubPackage::from_package(name.clone(), None, None, MarkerTree::TRUE)
2893+
}
2894+
};
2895+
2896+
let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2897+
let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2898+
self.pubgrub
2899+
.add_proxy_package(proxy_package, base_package_id, version.clone());
2900+
}
2901+
28732902
let conflict = self.pubgrub.add_package_version_dependencies(
28742903
self.next,
28752904
for_version.clone(),

crates/uv/tests/it/lock.rs

Lines changed: 40 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,36 @@ 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 == 'linux'",
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 == 'linux'):
31580+
╰─▶ Because your project depends on anyio{sys_platform == 'linux'} and anyio{python_full_version < '3.14'}>=4.4.0, we can conclude that your project's requirements are unsatisfiable.
31581+
");
31582+
31583+
Ok(())
31584+
}

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)