diff --git a/news/10479.feature.rst b/news/10479.feature.rst new file mode 100644 index 00000000000..e070c0e163a --- /dev/null +++ b/news/10479.feature.rst @@ -0,0 +1 @@ +New resolver: When backtracking prefer the dependencies which caused the most recent backtracking. In some cases this will significantly reduce the amount of backtracking required. diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index c2203933e40..f1d295aeb6a 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -71,7 +71,8 @@ def get_preference( identifier: str, resolutions: Mapping[str, Candidate], candidates: Mapping[str, Iterator[Candidate]], - information: Mapping[str, Iterable["PreferenceInformation"]], + information: Mapping[str, Iterator["PreferenceInformation"]], + backtrack_causes: Sequence["RequirementInformation"], ) -> "Preference": """Produce a sort key for given requirement based on preference. @@ -112,9 +113,9 @@ def get_preference( for _, parent in information[identifier] ) inferred_depth = min(d for d in parent_depths) + 1.0 + self._known_depths[identifier] = inferred_depth else: inferred_depth = 1.0 - self._known_depths[identifier] = inferred_depth requested_order = self._user_requested.get(identifier, math.inf) @@ -132,11 +133,17 @@ def get_preference( # while we work on "proper" branch pruning techniques. delay_this = identifier == "setuptools" + # Prefer the causes of backtracking on the assumption that the problem + # resolving the dependency tree is related to the failures that caused + # the backtracking + backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes) + return ( not requires_python, delay_this, not direct, not pinned, + not backtrack_cause, inferred_depth, requested_order, not unfree, @@ -195,3 +202,14 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: with_requires = not self._ignore_dependencies return [r for r in candidate.iter_dependencies(with_requires) if r is not None] + + @staticmethod + def is_backtrack_cause( + identifier: str, backtrack_causes: Sequence["RequirementInformation"] + ) -> bool: + for backtrack_cause in backtrack_causes: + if identifier == backtrack_cause.requirement.name: + return True + if backtrack_cause.parent and identifier == backtrack_cause.parent.name: + return True + return False