diff --git a/src/resolvelib/providers.py b/src/resolvelib/providers.py index e99d87e..b6ce58c 100644 --- a/src/resolvelib/providers.py +++ b/src/resolvelib/providers.py @@ -9,14 +9,7 @@ def identify(self, requirement_or_candidate): """ raise NotImplementedError - def get_preference( - self, - identifier, - resolutions, - candidates, - information, - backtrack_causes, - ): + def get_preference(self, identifier, resolutions, candidates, information): """Produce a sort key for given requirement based on preference. The preference is defined as "I think this requirement should be @@ -32,8 +25,6 @@ def get_preference( Each value is an iterator of candidates. :param information: Mapping of requirement information of each package. Each value is an iterator of *requirement information*. - :param backtrack_causes: Sequence of requirement information that were - the requirements that caused the resolver to most recently backtrack. A *requirement information* instance is a named tuple with two members: diff --git a/src/resolvelib/providers.pyi b/src/resolvelib/providers.pyi index ec05419..e985395 100644 --- a/src/resolvelib/providers.pyi +++ b/src/resolvelib/providers.pyi @@ -24,7 +24,6 @@ class AbstractProvider(Generic[RT, CT, KT]): resolutions: Mapping[KT, CT], candidates: Mapping[KT, Iterator[CT]], information: Mapping[KT, Iterator[RequirementInformation[RT, CT]]], - backtrack_causes: Sequence[RequirementInformation[RT, CT]], ) -> Preference: ... def find_matches( self, diff --git a/src/resolvelib/reporters.py b/src/resolvelib/reporters.py index 688b5e1..c236e60 100644 --- a/src/resolvelib/reporters.py +++ b/src/resolvelib/reporters.py @@ -41,3 +41,12 @@ def rejecting_candidate(self, criterion, candidate): def pinning(self, candidate): """Called when adding a candidate to the potential solution.""" + + def backtracking_on(self, names, unsatisfied_names): + """Called when resolver identifies specific projects causing backtracking. + + :param causes: The names of projects causing the backtracking. + :param causes: The names of projects and their parents that are + currently unsatisfied by the resolver. + + """ diff --git a/src/resolvelib/reporters.pyi b/src/resolvelib/reporters.pyi index b2ad286..726475f 100644 --- a/src/resolvelib/reporters.pyi +++ b/src/resolvelib/reporters.pyi @@ -9,3 +9,4 @@ class BaseReporter: def rejecting_candidate(self, criterion: Any, candidate: Any) -> Any: ... def resolving_conflicts(self, causes: Any) -> Any: ... def pinning(self, candidate: Any) -> Any: ... + def backtracking_on(self, names: set[str], unsatisfied_names:set[str]) - > Any: ... diff --git a/src/resolvelib/resolvers.py b/src/resolvelib/resolvers.py index 2c3d0e3..15c4910 100644 --- a/src/resolvelib/resolvers.py +++ b/src/resolvelib/resolvers.py @@ -211,7 +211,6 @@ def _get_preference(self, name): self.state.criteria, operator.attrgetter("information"), ), - backtrack_causes=self.state.backtrack_causes, ) def _is_current_pin_satisfying(self, name, criterion): @@ -418,10 +417,29 @@ def resolve(self, requirements, max_rounds): return self.state # keep track of satisfied names to calculate diff after pinning - satisfied_names = set(self.state.criteria.keys()) - set( - unsatisfied_names + unsatisfied_names_set = set(unsatisfied_names) + satisfied_names = ( + set(self.state.criteria.keys()) - unsatisfied_names_set ) + # If backtrack causes prefer them and their parents first + if self.state.backtrack_causes: + backtrack_cause_names = set() + for backtrack_cause in self.state.backtrack_causes: + backtrack_cause_names.add(backtrack_cause.requirement.name) + if backtrack_cause.parent: + backtrack_cause_names.add(backtrack_cause.parent.name) + + unsatisfied_causes_names = unsatisfied_names_set & ( + backtrack_cause_names + ) + if unsatisfied_causes_names: + unsatisfied_names = list(unsatisfied_causes_names) + + self._r.backtracking_on( + backtrack_cause_names, unsatisfied_causes_names + ) + # Choose the most preferred unpinned criterion to try. name = min(unsatisfied_names, key=self._get_preference) failure_causes = self._attempt_to_pin_criterion(name) diff --git a/tests/functional/cocoapods/test_resolvers_cocoapods.py b/tests/functional/cocoapods/test_resolvers_cocoapods.py index 12dff46..46bbcd2 100644 --- a/tests/functional/cocoapods/test_resolvers_cocoapods.py +++ b/tests/functional/cocoapods/test_resolvers_cocoapods.py @@ -174,14 +174,7 @@ def __init__(self, filename): def identify(self, requirement_or_candidate): return requirement_or_candidate.name - def get_preference( - self, - identifier, - resolutions, - candidates, - information, - backtrack_causes, - ): + def get_preference(self, identifier, resolutions, candidates, information): return sum(1 for _ in candidates[identifier]) def _iter_matches(self, name, requirements, incompatibilities): diff --git a/tests/functional/python/test_resolvers_python.py b/tests/functional/python/test_resolvers_python.py index 2b6de36..f0a4566 100644 --- a/tests/functional/python/test_resolvers_python.py +++ b/tests/functional/python/test_resolvers_python.py @@ -71,14 +71,7 @@ def identify(self, requirement_or_candidate): return "{}[{}]".format(name, extras_str) return name - def get_preference( - self, - identifier, - resolutions, - candidates, - information, - backtrack_causes, - ): + def get_preference(self, identifier, resolutions, candidates, information): transitive = all(p is not None for _, p in information[identifier]) return (transitive, identifier) diff --git a/tests/functional/swift-package-manager/test_resolvers_swift.py b/tests/functional/swift-package-manager/test_resolvers_swift.py index ad0e48f..7656027 100644 --- a/tests/functional/swift-package-manager/test_resolvers_swift.py +++ b/tests/functional/swift-package-manager/test_resolvers_swift.py @@ -78,14 +78,7 @@ def __init__(self, filename): def identify(self, requirement_or_candidate): return requirement_or_candidate.container["identifier"] - def get_preference( - self, - identifier, - resolutions, - candidates, - information, - backtrack_causes, - ): + def get_preference(self, identifier, resolutions, candidates, information): return sum(1 for _ in candidates[identifier]) def _iter_matches(self, identifier, requirements, incompatibilities):