diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index 40a641a2a4d..2ac3933b404 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -5,7 +5,7 @@ from .base import Constraint if MYPY_CHECK_RUNNING: - from typing import Any, Dict, Iterable, Optional, Sequence, Set, Tuple, Union + from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, Union from .base import Candidate, Requirement from .factory import Factory @@ -46,7 +46,7 @@ def __init__( constraints, # type: Dict[str, Constraint] ignore_dependencies, # type: bool upgrade_strategy, # type: str - user_requested, # type: Set[str] + user_requested, # type: Dict[str, int] ): # type: (...) -> None self._factory = factory @@ -77,7 +77,8 @@ def get_preference( * If equal, prefer if any requirements contain ``===`` and ``==``. * If equal, prefer if requirements include version constraints, e.g. ``>=`` and ``<``. - * If equal, prefer user-specified (non-transitive) requirements. + * If equal, prefer user-specified (non-transitive) requirements, and + order user-specified requirements by the order they are specified. * If equal, order alphabetically for consistency (helps debuggability). """ @@ -115,8 +116,8 @@ def _get_restrictive_rating(requirements): return 3 restrictive = _get_restrictive_rating(req for req, _ in information) - transitive = all(parent is not None for _, parent in information) key = next(iter(candidates)).name if candidates else "" + order = self._user_requested.get(key, float("inf")) # HACK: Setuptools have a very long and solid backward compatibility # track record, and extremely few projects would request a narrow, @@ -128,7 +129,7 @@ def _get_restrictive_rating(requirements): # while we work on "proper" branch pruning techniques. delay_this = (key == "setuptools") - return (delay_this, restrictive, transitive, key) + return (delay_this, restrictive, order, key) def find_matches(self, requirements): # type: (Sequence[Requirement]) -> Iterable[Candidate] diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py index d30d696fc46..d02a49c7daa 100644 --- a/src/pip/_internal/resolution/resolvelib/resolver.py +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -79,9 +79,9 @@ def resolve(self, root_reqs, check_supported_wheels): # type: (List[InstallRequirement], bool) -> RequirementSet constraints = {} # type: Dict[str, Constraint] - user_requested = set() # type: Set[str] + user_requested = {} # type: Dict[str, int] requirements = [] - for req in root_reqs: + for i, req in enumerate(root_reqs): if req.constraint: # Ensure we only accept valid constraints problem = check_invalid_constraint_type(req) @@ -96,7 +96,9 @@ def resolve(self, root_reqs, check_supported_wheels): constraints[name] = Constraint.from_ireq(req) else: if req.user_supplied and req.name: - user_requested.add(canonicalize_name(req.name)) + canonical_name = canonicalize_name(req.name) + if canonical_name not in user_requested: + user_requested[canonical_name] = i r = self.factory.make_requirement_from_install_req( req, requested_extras=(), ) diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py index 9c1c9e5c4b3..f77b98ee193 100644 --- a/tests/unit/resolution_resolvelib/conftest.py +++ b/tests/unit/resolution_resolvelib/conftest.py @@ -69,5 +69,5 @@ def provider(factory): constraints={}, ignore_dependencies=False, upgrade_strategy="to-satisfy-only", - user_requested=set(), + user_requested={}, )