diff --git a/Cargo.lock b/Cargo.lock index ae0cfab5955e..ba9e89f412e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2673,7 +2673,7 @@ dependencies = [ [[package]] name = "pubgrub" version = "0.2.1" -source = "git+https://github.com/astral-sh/pubgrub?rev=57832d0588fbb7aab824813481104761dc1c7740#57832d0588fbb7aab824813481104761dc1c7740" +source = "git+https://github.com/astral-sh/pubgrub?rev=05e8d12cea8d72c6d2d017900e478d0abd28fef4#05e8d12cea8d72c6d2d017900e478d0abd28fef4" dependencies = [ "indexmap", "log", @@ -5672,7 +5672,7 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version-ranges" version = "0.1.1" -source = "git+https://github.com/astral-sh/pubgrub?rev=57832d0588fbb7aab824813481104761dc1c7740#57832d0588fbb7aab824813481104761dc1c7740" +source = "git+https://github.com/astral-sh/pubgrub?rev=05e8d12cea8d72c6d2d017900e478d0abd28fef4#05e8d12cea8d72c6d2d017900e478d0abd28fef4" dependencies = [ "smallvec", ] diff --git a/Cargo.toml b/Cargo.toml index b8591eb7d27b..634ce793eebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,7 +130,7 @@ petgraph = { version = "0.6.5" } platform-info = { version = "2.0.3" } proc-macro2 = { version = "1.0.86" } procfs = { version = "0.17.0", default-features = false, features = ["flate2"] } -pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "57832d0588fbb7aab824813481104761dc1c7740" } +pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "05e8d12cea8d72c6d2d017900e478d0abd28fef4" } quote = { version = "1.0.37" } rayon = { version = "1.10.0" } reflink-copy = { version = "0.1.19" } @@ -175,7 +175,7 @@ unicode-width = { version = "0.1.13" } unscanny = { version = "0.1.0" } url = { version = "2.5.2", features = ["serde"] } urlencoding = { version = "2.1.3" } -version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "57832d0588fbb7aab824813481104761dc1c7740" } +version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "05e8d12cea8d72c6d2d017900e478d0abd28fef4" } walkdir = { version = "2.5.0" } which = { version = "7.0.0", features = ["regex"] } windows-registry = { version = "0.3.0" } diff --git a/crates/uv-resolver/src/dependency_provider.rs b/crates/uv-resolver/src/dependency_provider.rs index 8fd878b3da58..66ce64d1f180 100644 --- a/crates/uv-resolver/src/dependency_provider.rs +++ b/crates/uv-resolver/src/dependency_provider.rs @@ -1,6 +1,6 @@ use std::convert::Infallible; -use pubgrub::{Dependencies, DependencyProvider, Range}; +use pubgrub::{Dependencies, DependencyProvider, PackageResolutionStatistics, Range}; use uv_pep440::Version; @@ -17,13 +17,17 @@ impl DependencyProvider for UvDependencyProvider { type V = Version; type VS = Range; type M = UnavailableReason; + type Priority = Option; + type Err = Infallible; - fn prioritize(&self, _package: &Self::P, _range: &Self::VS) -> Self::Priority { + fn prioritize( + &self, + _package: &Self::P, + _range: &Self::VS, + _stats: &PackageResolutionStatistics, + ) -> Self::Priority { unimplemented!() } - type Priority = Option; - - type Err = Infallible; fn choose_version( &self, diff --git a/crates/uv-resolver/src/pubgrub/priority.rs b/crates/uv-resolver/src/pubgrub/priority.rs index 3c7206055dd4..e11aba0508f1 100644 --- a/crates/uv-resolver/src/pubgrub/priority.rs +++ b/crates/uv-resolver/src/pubgrub/priority.rs @@ -1,7 +1,7 @@ -use std::cmp::Reverse; - use pubgrub::Range; use rustc_hash::FxHashMap; +use std::cmp::Reverse; +use std::collections::hash_map::OccupiedEntry; use crate::fork_urls::ForkUrls; use uv_normalize::PackageName; @@ -40,12 +40,7 @@ impl PubGrubPriorities { match self.0.entry(name.clone()) { std::collections::hash_map::Entry::Occupied(mut entry) => { // Preserve the original index. - let index = match entry.get() { - PubGrubPriority::Unspecified(Reverse(index)) => *index, - PubGrubPriority::Singleton(Reverse(index)) => *index, - PubGrubPriority::DirectUrl(Reverse(index)) => *index, - PubGrubPriority::Root => next, - }; + let index = Self::get_index(&entry).unwrap_or(next); // Compute the priority. let priority = if urls.get(name).is_some() { @@ -53,6 +48,14 @@ impl PubGrubPriorities { } else if version.as_singleton().is_some() { PubGrubPriority::Singleton(Reverse(index)) } else { + // Keep the conflict-causing packages to avoid loops where we seesaw between + // `Unspecified` and `Conflict*`. + if matches!( + entry.get(), + PubGrubPriority::ConflictEarly(_) | PubGrubPriority::ConflictLate(_) + ) { + return; + } PubGrubPriority::Unspecified(Reverse(index)) }; @@ -77,6 +80,17 @@ impl PubGrubPriorities { } } + fn get_index(entry: &OccupiedEntry) -> Option { + match entry.get() { + PubGrubPriority::ConflictLate(Reverse(index)) + | PubGrubPriority::Unspecified(Reverse(index)) + | PubGrubPriority::ConflictEarly(Reverse(index)) + | PubGrubPriority::Singleton(Reverse(index)) + | PubGrubPriority::DirectUrl(Reverse(index)) => Some(*index), + PubGrubPriority::Root => None, + } + } + /// Return the [`PubGrubPriority`] of the given package, if it exists. pub(crate) fn get(&self, package: &PubGrubPackage) -> Option { match &**package { @@ -88,6 +102,79 @@ impl PubGrubPriorities { PubGrubPackageInner::Package { name, .. } => self.0.get(name).copied(), } } + + /// Mark a package as prioritized by setting it to [`PubGrubPriority::ConflictEarly`], if it + /// doesn't have a higher priority already. + /// + /// Returns whether the priority was changed, i.e., it's the first time we hit this condition + /// for the package. + pub(crate) fn mark_conflict_early(&mut self, package: &PubGrubPackage) -> bool { + let next = self.0.len(); + let Some(name) = package.name_no_root() else { + // Not a correctness bug + if cfg!(debug_assertions) { + panic!("URL packages must not be involved in conflict handling") + } else { + return false; + } + }; + match self.0.entry(name.clone()) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + if matches!( + entry.get(), + PubGrubPriority::ConflictEarly(_) | PubGrubPriority::Singleton(_) + ) { + // Already in the right category + return false; + }; + let index = Self::get_index(&entry).unwrap_or(next); + entry.insert(PubGrubPriority::ConflictEarly(Reverse(index))); + true + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(PubGrubPriority::ConflictEarly(Reverse(next))); + true + } + } + } + + /// Mark a package as prioritized by setting it to [`PubGrubPriority::ConflictLate`], if it + /// doesn't have a higher priority already. + /// + /// Returns whether the priority was changed, i.e., it's the first time this package was + /// marked as conflicting above the threshold. + pub(crate) fn mark_conflict_late(&mut self, package: &PubGrubPackage) -> bool { + let next = self.0.len(); + let Some(name) = package.name_no_root() else { + // Not a correctness bug + if cfg!(debug_assertions) { + panic!("URL packages must not be involved in conflict handling") + } else { + return false; + } + }; + match self.0.entry(name.clone()) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + // The ConflictEarly` match avoids infinite loops. + if matches!( + entry.get(), + PubGrubPriority::ConflictLate(_) + | PubGrubPriority::ConflictEarly(_) + | PubGrubPriority::Singleton(_) + ) { + // Already in the right category + return false; + }; + let index = Self::get_index(&entry).unwrap_or(next); + entry.insert(PubGrubPriority::ConflictLate(Reverse(index))); + true + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(PubGrubPriority::ConflictLate(Reverse(next))); + true + } + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -101,6 +188,15 @@ pub(crate) enum PubGrubPriority { /// in the dependency graph. Unspecified(Reverse), + /// Selected version of this package were often the culprit of rejecting another package, so + /// it's deprioritized behind `ConflictEarly`. It's still the higher than `Unspecified` to + /// conflict before selecting unrelated packages. + ConflictLate(Reverse), + + /// Selected version of this package were often rejected, so it's prioritized over + /// `ConflictLate`. + ConflictEarly(Reverse), + /// The version range is constrained to a single version (e.g., with the `==` operator). Singleton(Reverse), diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index f041a1d6f104..75a83a1696e5 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -13,7 +13,7 @@ use dashmap::DashMap; use either::Either; use futures::{FutureExt, StreamExt}; use itertools::Itertools; -use pubgrub::{Id, Incompatibility, Range, State}; +use pubgrub::{Id, IncompId, Incompatibility, Range, Ranges, State}; use rustc_hash::{FxHashMap, FxHashSet}; use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::oneshot; @@ -89,6 +89,9 @@ mod provider; mod reporter; mod urls; +/// The number of conflicts a package may accumulate before we re-prioritize and backtrack. +const CONFLICT_THRESHOLD: usize = 5; + pub struct Resolver { state: ResolverState, provider: Provider, @@ -332,16 +335,27 @@ impl ResolverState { + // If unit propagation failed, there is no solution. + return Err(self.convert_no_solution_err( + err, + state.fork_urls, + &state.fork_indexes, + state.env, + &visited, + &self.locations, + &self.capabilities, + )); + } + Ok(conflicts) => { + for (affected, incompatibility) in conflicts { + // Conflict tracking: If there was a conflict, track affected and + // culprit for all root cause incompatibilities + state.record_conflict(affected, None, incompatibility); + } + } } // Pre-visit all candidate packages, to allow metadata to be fetched in parallel. @@ -359,7 +373,19 @@ impl ResolverState ResolverState ResolverState, + version: Option<&Version>, + incompatibility: IncompId, UnavailableReason>, + ) { + let mut culprit_is_real = false; + for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() { + if incompatible == affected { + continue; + } + if self.pubgrub.package_store[affected].name() + == self.pubgrub.package_store[incompatible].name() + { + // Don't track conflicts between a marker package and the main package, when the + // marker is "copying" the obligations from the main package through conflicts. + continue; + } + culprit_is_real = true; + let culprit_count = self + .conflict_tracker + .culprit + .entry(incompatible) + .or_default(); + *culprit_count += 1; + if *culprit_count == CONFLICT_THRESHOLD { + self.conflict_tracker.deprioritize.push(incompatible); + } + } + // Don't track conflicts between a marker package and the main package, when the + // marker is "copying" the obligations from the main package through conflicts. + if culprit_is_real { + if tracing::enabled!(Level::DEBUG) { + let incompatibility = self.pubgrub.incompatibility_store[incompatibility] + .iter() + .map(|(package, _term)| { + format!("{}", self.pubgrub.package_store[package].clone(),) + }) + .join(", "); + if let Some(version) = version { + debug!( + "Recording dependency conflict of {}=={} from incompatibility of ({})", + self.pubgrub.package_store[affected], version, incompatibility + ); + } else { + debug!( + "Recording unit propagation conflict of {} from incompatibility of ({})", + self.pubgrub.package_store[affected], incompatibility + ); + } + } + + let affected_count = self.conflict_tracker.affected.entry(self.next).or_default(); + *affected_count += 1; + if *affected_count == CONFLICT_THRESHOLD { + self.conflict_tracker.prioritize.push(self.next); + } + } + } + fn add_unavailable_version(&mut self, version: Version, reason: UnavailableVersion) { // Incompatible requires-python versions are special in that we track // them as incompatible dependencies instead of marking the package version @@ -3221,3 +3376,19 @@ fn find_conflicting_extra(conflicting: &Conflicts, reqs: &[Requirement]) -> Opti } None } + +#[derive(Debug, Default, Clone)] +struct ConflictTracker { + /// How often a decision on the package was discarded due to another package decided earlier. + affected: FxHashMap, usize>, + /// Package(s) to be prioritized after the next unit propagation + /// + /// Distilled from `affected` for fast checking in the hot loop. + prioritize: Vec>, + /// How often a package was decided earlier and caused another package to be discarded. + culprit: FxHashMap, usize>, + /// Package(s) to be de-prioritized after the next unit propagation + /// + /// Distilled from `culprit` for fast checking in the hot loop. + deprioritize: Vec>, +} diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index fec00f2b9e91..d1079bbc30d7 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -14433,6 +14433,7 @@ fn lock_explicit_default_index() -> Result<()> { DEBUG Adding transitive dependency for project==0.1.0: anyio* DEBUG Searching for a compatible version of anyio (*) DEBUG No compatible version found for: anyio + DEBUG Recording unit propagation conflict of anyio from incompatibility of (project) DEBUG Searching for a compatible version of project @ file://[TEMP_DIR]/ (<0.1.0 | >0.1.0) DEBUG No compatible version found for: project × No solution found when resolving dependencies: diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap index ef2424c91030..8e22a4bdc679 100644 --- a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap +++ b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap @@ -2905,18 +2905,20 @@ wheels = [ [[package]] name = "protobuf" -version = "4.25.4" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/ab/cb61a4b87b2e7e6c312dce33602bd5884797fd054e0e53205f1c27cf0f66/protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d", size = 380283 } +sdist = { url = "https://files.pythonhosted.org/packages/55/5b/e3d951e34f8356e5feecacd12a8e3b258a1da6d9a03ad1770f28925f29bc/protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2", size = 216768 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/43/27b48d9040763b78177d3083e16c70dba6e3c3ee2af64b659f6332c2b06e/protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4", size = 392409 }, - { url = "https://files.pythonhosted.org/packages/0c/d4/589d673ada9c4c62d5f155218d7ff7ac796efb9c6af95b0bd29d438ae16e/protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d", size = 413398 }, - { url = "https://files.pythonhosted.org/packages/34/ca/bf85ffe3dd16f1f2aaa6c006da8118800209af3da160ae4d4f47500eabd9/protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b", size = 394160 }, - { url = "https://files.pythonhosted.org/packages/68/1d/e8961af9a8e534d66672318d6b70ea8e3391a6b13e16a29b039e4a99c214/protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835", size = 293700 }, - { url = "https://files.pythonhosted.org/packages/ca/6c/cc7ab2fb3a4a7f07f211d8a7bbb76bba633eb09b148296dbd4281e217f95/protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040", size = 294612 }, - { url = "https://files.pythonhosted.org/packages/a4/b5/f7e2460dec8347d67e6108bef6ad3291c76e38c898a1087e2c836c02951e/protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca", size = 392490 }, - { url = "https://files.pythonhosted.org/packages/c7/0b/15bd1a224e5e5744a0dcccf11bcd5dc1405877be38e477b1359d7c2c3737/protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f", size = 413357 }, - { url = "https://files.pythonhosted.org/packages/b5/95/0ba7f66934a0a798006f06fc3d74816da2b7a2bcfd9b98c53d26f684c89e/protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978", size = 156464 }, + { url = "https://files.pythonhosted.org/packages/28/55/b80e8567ec327c060fa39b242392e25690c8899c489ecd7bb65b46b7bb55/protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99", size = 918427 }, + { url = "https://files.pythonhosted.org/packages/31/be/80a9c6f16dfa4d41be3edbe655349778ae30882407fa8275eb46b4d34854/protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e", size = 1051042 }, + { url = "https://files.pythonhosted.org/packages/db/96/948d3fcc1fa816e7ae1d27af59b9d8c5c5e582f3994fd14394f31da95b99/protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c", size = 780167 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/fc6feb366b0a9f28e0a2de3b062667c521cd9517d4ff55077b8f351ba2f3/protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7", size = 904029 }, + { url = "https://files.pythonhosted.org/packages/00/e7/d23c439c55c90ae2e52184363162f7079ca3e7d86205b411d4e9dc266f81/protobuf-3.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b", size = 982826 }, + { url = "https://files.pythonhosted.org/packages/99/25/5825472ecd911f4ac2ac4e9ab039a48b6d03874e2add92fb633e080bf3eb/protobuf-3.20.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b", size = 918423 }, + { url = "https://files.pythonhosted.org/packages/c7/df/ec3ecb8c940b36121c7b77c10acebf3d1c736498aa2f1fe3b6231ee44e76/protobuf-3.20.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402", size = 1019250 }, + { url = "https://files.pythonhosted.org/packages/36/8b/433071fed0058322090a55021bdc8da76d16c7bc9823f5795797803dd6d0/protobuf-3.20.3-cp39-cp39-win32.whl", hash = "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480", size = 780270 }, + { url = "https://files.pythonhosted.org/packages/11/a5/e52b731415ad6ef3d841e9e6e337a690249e800cc7c06f0749afab26348c/protobuf-3.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7", size = 904215 }, + { url = "https://files.pythonhosted.org/packages/8d/14/619e24a4c70df2901e1f4dbc50a6291eb63a759172558df326347dce1f0d/protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db", size = 162128 }, ] [[package]] @@ -4538,17 +4540,18 @@ wheels = [ [[package]] name = "tf2onnx" -version = "1.8.4" +version = "1.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "onnx" }, + { name = "protobuf" }, { name = "requests" }, { name = "six" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/db/32/33ce509a79c207a39cf04bfa3ec3353da15d1e6553a6ad912f117cc29130/tf2onnx-1.8.4-py3-none-any.whl", hash = "sha256:1ebabb96c914da76e23222b6107a8b248a024bf259d77f027e6690099512d457", size = 345298 }, + { url = "https://files.pythonhosted.org/packages/3f/48/826db3d02645d84e7ee5d5ce8407f771057d40fe224d9c3e89536674ccef/tf2onnx-1.16.1-py3-none-any.whl", hash = "sha256:90fb5f62575896d47884d27dc313cfebff36b8783e1094335ad00824ce923a8a", size = 455820 }, ] [[package]] diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap index 070fb7d61e1d..9f2c7526eb44 100644 --- a/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap +++ b/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap @@ -1840,20 +1840,20 @@ wheels = [ [[package]] name = "mypy" -version = "1.11.1" +version = "1.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/9c/a4b3bda53823439cf395db8ecdda6229a83f9bf201714a68a15190bb2919/mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08", size = 3078369 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/b9/81e4c6dbb1ec1e72503de3ff2c5fe4b7f224e04613b670f8b9004cd8a4dd/mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0", size = 3022304 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/b1/62d8ce619493a5364dda4f410912aa12c27126926e8fb8393edca0664640/mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5", size = 10858723 }, - { url = "https://files.pythonhosted.org/packages/fe/aa/2ad15a318bc6a17b7f23e1641a624603949904f6131e09681f40340fb875/mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca", size = 10038078 }, - { url = "https://files.pythonhosted.org/packages/4d/7f/77feb389d91603f55b3c4e3e16ccf8752bce007ed73ca921e42c9a5dff12/mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de", size = 12420213 }, - { url = "https://files.pythonhosted.org/packages/bc/5b/907b4681f68e7ee2e2e88eed65c514cf6406b8f2f83b243ea79bd4eddb97/mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809", size = 12898278 }, - { url = "https://files.pythonhosted.org/packages/5b/b3/2a83be637825d7432b8e6a51e45d02de4f463b6c7ec7164a45009a7cf477/mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72", size = 9564438 }, - { url = "https://files.pythonhosted.org/packages/f8/d4/4960d0df55f30a7625d9c3c9414dfd42f779caabae137ef73ffaed0c97b9/mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54", size = 2619257 }, + { url = "https://files.pythonhosted.org/packages/38/cf/0645128c6edf70eb9b9687ad42fcb61ea344a7927ed2b78ce2275282fe87/mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a", size = 10740526 }, + { url = "https://files.pythonhosted.org/packages/19/c9/10842953066265e6063c41a85bbee3b877501947c970ea84a1db5f11d32e/mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84", size = 9898375 }, + { url = "https://files.pythonhosted.org/packages/e4/9e/551e897f67c5d67aa1976bc3b4951f297d1daf07250c421bb045b2613350/mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f", size = 12602338 }, + { url = "https://files.pythonhosted.org/packages/2b/a4/55e3635253e5fa7051674dd5a67582f08b0ba8823e1fdbf7241ed5b32d4e/mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b", size = 12680741 }, + { url = "https://files.pythonhosted.org/packages/7a/cc/aa881ad051f99915887db0b5de8facc0e224295be22f92178c8f77fd8359/mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e", size = 9393661 }, + { url = "https://files.pythonhosted.org/packages/2b/ee/d53a3d4792a09b6cd757978951d6dcf8b10825a8b8522b68e9b5eb53b9a1/mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a", size = 2580108 }, ] [[package]] @@ -1867,14 +1867,14 @@ wheels = [ [[package]] name = "mypy-zope" -version = "0.1.3" +version = "1.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy" }, { name = "zope-interface" }, { name = "zope-schema" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/45/ba2c0ac5487a70ba26209bc32773ead684092c40e122c5d3c50f8587fa0f/mypy-zope-0.1.3.tar.gz", hash = "sha256:4247f7ad0202cc82f12ee0b14a3d7b31adfeff1b6d5ca176168853d64c672dd1", size = 23600 } +sdist = { url = "https://files.pythonhosted.org/packages/68/8f/4be355338ff798e7cb6afbc01993bd952e834b3718e47eba626d4822b331/mypy_zope-1.0.5.tar.gz", hash = "sha256:2440406d49c0e1199c1cd819c92a2c4957de65579c6abc8a081c927f4bdc8d49", size = 33936 } [[package]] name = "myst-parser" diff --git a/docs/reference/resolver-internals.md b/docs/reference/resolver-internals.md index 5381ac04898d..4eb272b328be 100644 --- a/docs/reference/resolver-internals.md +++ b/docs/reference/resolver-internals.md @@ -63,6 +63,9 @@ involved packages. For more details on the PubGrub algorithm, see [Internals of the PubGrub algorithm](https://pubgrub-rs-guide.pages.dev/internals/intro). +In addition to PubGrub's base algorithm, we also use a heuristic that backtracks and switches the +order of two packages if they have been conflicting too much. + ## Forking Python resolvers historically didn't support backtracking, and even with backtracking, resolution @@ -70,7 +73,7 @@ was usually limited to single environment, which one specific architecture, oper version, and Python implementation. Some packages use contradictory requirements for different environments, for example: -```python +``` numpy>=2,<3 ; python_version >= "3.11" numpy>=1.16,<2 ; python_version < "3.11" ``` @@ -85,7 +88,7 @@ In the above example, the partial solution would be split into two resolutions, If markers overlap or are missing a part of the marker space, the resolver splits additional times — there can be many forks per package. For example, given: -```python +``` flask > 1 ; sys_platform == 'darwin' flask > 2 ; sys_platform == 'win32' flask