Skip to content

Commit

Permalink
Merge pull request #10481 from notatallshaw/prefer_failures
Browse files Browse the repository at this point in the history
  • Loading branch information
pradyunsg authored Oct 9, 2021
2 parents db496cb + 1e3c127 commit 9f18a40
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 10 deletions.
1 change: 1 addition & 0 deletions news/10479.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When backtracking during dependency resolution, prefer the dependencies which are involved in the most recent conflict. This can significantly reduce the amount of backtracking required.
1 change: 1 addition & 0 deletions news/resolvelib.vendor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Upgrade resolvelib to 0.8.0
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def vendoring(session: nox.Session) -> None:
session.install("vendoring~=1.0.0")

if "--upgrade" not in session.posargs:
session.run("vendoring", "sync", ".", "-v")
session.run("vendoring", "sync", "-v")
return

def pinned_requirements(path: Path) -> Iterator[Tuple[str, str]]:
Expand Down
20 changes: 19 additions & 1 deletion src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ def __init__(
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
return requirement_or_candidate.name

def get_preference(
def get_preference( # type: ignore
self,
identifier: str,
resolutions: Mapping[str, Candidate],
candidates: Mapping[str, Iterator[Candidate]],
information: Mapping[str, Iterable["PreferenceInformation"]],
backtrack_causes: Sequence["PreferenceInformation"],
) -> "Preference":
"""Produce a sort key for given requirement based on preference.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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["PreferenceInformation"]
) -> 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
2 changes: 1 addition & 1 deletion src/pip/_vendor/resolvelib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"ResolutionTooDeep",
]

__version__ = "0.7.1"
__version__ = "0.8.0"


from .providers import AbstractProvider, AbstractResolver
Expand Down
11 changes: 10 additions & 1 deletion src/pip/_vendor/resolvelib/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ def identify(self, requirement_or_candidate):
"""
raise NotImplementedError

def get_preference(self, identifier, resolutions, candidates, information):
def get_preference(
self,
identifier,
resolutions,
candidates,
information,
backtrack_causes,
):
"""Produce a sort key for given requirement based on preference.
The preference is defined as "I think this requirement should be
Expand All @@ -25,6 +32,8 @@ def get_preference(self, identifier, resolutions, candidates, information):
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:
Expand Down
18 changes: 14 additions & 4 deletions src/pip/_vendor/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def __init__(self, round_count):


# Resolution state in a round.
State = collections.namedtuple("State", "mapping criteria")
State = collections.namedtuple("State", "mapping criteria backtrack_causes")


class Resolution(object):
Expand Down Expand Up @@ -131,6 +131,7 @@ def _push_new_state(self):
state = State(
mapping=base.mapping.copy(),
criteria=base.criteria.copy(),
backtrack_causes=base.backtrack_causes[:],
)
self._states.append(state)

Expand Down Expand Up @@ -185,6 +186,7 @@ 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):
Expand Down Expand Up @@ -335,7 +337,13 @@ def resolve(self, requirements, max_rounds):
self._r.starting()

# Initialize the root state.
self._states = [State(mapping=collections.OrderedDict(), criteria={})]
self._states = [
State(
mapping=collections.OrderedDict(),
criteria={},
backtrack_causes=[],
)
]
for r in requirements:
try:
self._add_to_criteria(self.state.criteria, r, parent=None)
Expand Down Expand Up @@ -369,11 +377,13 @@ def resolve(self, requirements, max_rounds):
# Backtrack if pinning fails. The backtrack process puts us in
# an unpinned state, so we can work on it in the next round.
success = self._backtrack()
self.state.backtrack_causes[:] = [
i for c in failure_causes for i in c.information
]

# Dead ends everywhere. Give up.
if not success:
causes = [i for c in failure_causes for i in c.information]
raise ResolutionImpossible(causes)
raise ResolutionImpossible(self.state.backtrack_causes)
else:
# Pinning was successful. Push a new state to do another pin.
self._push_new_state()
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/vendor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ requests==2.26.0
chardet==4.0.0
idna==3.2
urllib3==1.26.7
resolvelib==0.7.1
resolvelib==0.8.0
setuptools==44.0.0
six==1.16.0
tenacity==8.0.1
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/resolution_resolvelib/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def build_requirement_information(
install_requirement = install_req_from_req_string(name)
# RequirementInformation is typed as a tuple, but it is a namedtupled.
# https://github.com/sarugaku/resolvelib/blob/7bc025aa2a4e979597c438ad7b17d2e8a08a364e/src/resolvelib/resolvers.pyi#L20-L22
requirement_information: PreferenceInformation = RequirementInformation(
requirement_information: "PreferenceInformation" = RequirementInformation(
requirement=SpecifierRequirement(install_requirement), # type: ignore[call-arg]
parent=parent,
)
Expand All @@ -46,6 +46,7 @@ def test_provider_known_depths(factory: Factory) -> None:
resolutions={},
candidates={},
information={root_requirement_name: root_requirement_information},
backtrack_causes=[],
)
assert provider._known_depths == {root_requirement_name: 1.0}

Expand All @@ -69,6 +70,7 @@ def test_provider_known_depths(factory: Factory) -> None:
root_requirement_name: root_requirement_information,
transative_requirement_name: transative_package_information,
},
backtrack_causes=[],
)
assert provider._known_depths == {
transative_requirement_name: 2.0,
Expand Down

0 comments on commit 9f18a40

Please sign in to comment.