Skip to content

Flip the switch on new resolver #9019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ addons:
stages:
- primary
- secondary
- experimental

jobs:
include:
Expand All @@ -32,25 +31,6 @@ jobs:
- env: GROUP=2
python: pypy2.7-7.1.1

# Test experimental stuff that are not part of the standard pip usage.
# Helpful for developers working on them to see how they're doing.
- stage: experimental
env:
- GROUP=1
- NEW_RESOLVER=1
- env:
- GROUP=2
- NEW_RESOLVER=1
- env:
- GROUP=3
- NEW_RESOLVER=1

fast_finish: true
allow_failures:
- env:
- GROUP=3
- NEW_RESOLVER=1

before_install: tools/travis/setup.sh
install: travis_retry tools/travis/install.sh
script: tools/travis/run.sh
Expand Down
1 change: 1 addition & 0 deletions news/9019.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Switch to the new dependency resolver by default.
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ markers =
mercurial: VCS: Mercurial
git: VCS: git
yaml: yaml based tests
fails_on_new_resolver: Does not yet work on the new resolver

[coverage:run]
branch = True
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ def check_list_path_option(options):
metavar='feature',
action='append',
default=[],
choices=[],
choices=['legacy-resolver'],
help=(
'Enable deprecated functionality, that will be removed in the future.'
),
Expand Down
43 changes: 39 additions & 4 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import os
from functools import partial

from pip._vendor.six import PY2

from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.command_context import CommandContextMixIn
Expand Down Expand Up @@ -195,7 +197,33 @@ def __init__(self, *args, **kw):
self.cmd_opts.add_option(cmdoptions.no_clean())

@staticmethod
def determine_resolver_variant(options):
# type: (Values) -> str
"""Determines which resolver should be used, based on the given options."""
# We didn't want to change things for Python 2, since it's nearly done with
# and we're using performance improvements that only work on Python 3.
if PY2:
if '2020-resolver' in options.features_enabled:
return "2020-resolver"
else:
return "legacy"

# Warn about the options that are gonna be removed.
if '2020-resolver' in options.features_enabled:
logger.warning(
"--use-feature=2020-resolver no longer has any effect, "
"since it is now the default dependency resolver in pip. "
"This will become an error in pip 21.0."
)

if "legacy-resolver" in options.deprecated_features_enabled:
return "legacy"

return "2020-resolver"

@classmethod
def make_requirement_preparer(
cls,
temp_build_dir, # type: TempDirectory
options, # type: Values
req_tracker, # type: RequirementTracker
Expand All @@ -211,7 +239,8 @@ def make_requirement_preparer(
temp_build_dir_path = temp_build_dir.path
assert temp_build_dir_path is not None

if '2020-resolver' in options.features_enabled:
resolver_variant = cls.determine_resolver_variant(options)
if resolver_variant == "2020-resolver":
lazy_wheel = 'fast-deps' in options.features_enabled
if lazy_wheel:
logger.warning(
Expand All @@ -223,6 +252,10 @@ def make_requirement_preparer(
)
else:
lazy_wheel = False
if 'fast-deps' in options.features_enabled:
logger.warning(
'fast-deps has no effect when used with the legacy resolver.'
)

return RequirementPreparer(
build_dir=temp_build_dir_path,
Expand All @@ -238,8 +271,9 @@ def make_requirement_preparer(
lazy_wheel=lazy_wheel,
)

@staticmethod
@classmethod
def make_resolver(
cls,
preparer, # type: RequirementPreparer
finder, # type: PackageFinder
options, # type: Values
Expand All @@ -250,7 +284,7 @@ def make_resolver(
force_reinstall=False, # type: bool
upgrade_strategy="to-satisfy-only", # type: str
use_pep517=None, # type: Optional[bool]
py_version_info=None # type: Optional[Tuple[int, ...]]
py_version_info=None, # type: Optional[Tuple[int, ...]]
):
# type: (...) -> BaseResolver
"""
Expand All @@ -261,10 +295,11 @@ def make_resolver(
isolated=options.isolated_mode,
use_pep517=use_pep517,
)
resolver_variant = cls.determine_resolver_variant(options)
# The long import name and duplicated invocation is needed to convince
# Mypy into correctly typechecking. Otherwise it would complain the
# "Resolver" class being redefined.
if '2020-resolver' in options.features_enabled:
if resolver_variant == "2020-resolver":
import pip._internal.resolution.resolvelib.resolver

return pip._internal.resolution.resolvelib.resolver.Resolver(
Expand Down
8 changes: 4 additions & 4 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def run(self, options, args):
if conflicts is not None:
self._warn_about_conflicts(
conflicts,
new_resolver='2020-resolver' in options.features_enabled,
resolver_variant=self.determine_resolver_variant(options),
)

installed_desc = ' '.join(items)
Expand Down Expand Up @@ -520,14 +520,14 @@ def _determine_conflicts(self, to_install):
)
return None

def _warn_about_conflicts(self, conflict_details, new_resolver):
# type: (ConflictDetails, bool) -> None
def _warn_about_conflicts(self, conflict_details, resolver_variant):
# type: (ConflictDetails, str) -> None
package_set, (missing, conflicting) = conflict_details
if not missing and not conflicting:
return

parts = [] # type: List[str]
if not new_resolver:
if resolver_variant == "legacy":
parts.append(
"After October 2020 you may experience errors when installing "
"or updating packages. This is because pip will change the "
Expand Down
52 changes: 28 additions & 24 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,11 @@ def pytest_addoption(parser):
help="keep temporary test directories",
)
parser.addoption(
"--new-resolver",
action="store_true",
default=False,
help="use new resolver in tests",
)
parser.addoption(
"--new-resolver-runtests",
action="store_true",
default=False,
help="run the skipped tests for the new resolver",
"--resolver",
action="store",
default="2020-resolver",
choices=["2020-resolver", "legacy"],
help="use given resolver in tests",
)
parser.addoption(
"--use-venv",
Expand All @@ -68,12 +63,6 @@ def pytest_collection_modifyitems(config, items):
if item.get_closest_marker('network') is not None:
item.add_marker(pytest.mark.flaky(reruns=3, reruns_delay=2))

if (item.get_closest_marker('fails_on_new_resolver') and
config.getoption("--new-resolver") and
not config.getoption("--new-resolver-runtests")):
item.add_marker(pytest.mark.skip(
'This test does not work with the new resolver'))

if six.PY3:
if (item.get_closest_marker('incompatible_with_test_venv') and
config.getoption("--use-venv")):
Expand Down Expand Up @@ -103,17 +92,32 @@ def pytest_collection_modifyitems(config, items):


@pytest.fixture(scope="session", autouse=True)
def use_new_resolver(request):
"""Set environment variable to make pip default to the new resolver.
def resolver_variant(request):
"""Set environment variable to make pip default to the correct resolver.
"""
new_resolver = request.config.getoption("--new-resolver")
resolver = request.config.getoption("--resolver")

# Handle the environment variables for this test.
features = set(os.environ.get("PIP_USE_FEATURE", "").split())
if new_resolver:
features.add("2020-resolver")
deprecated_features = set(os.environ.get("PIP_USE_DEPRECATED", "").split())

if six.PY3:
if resolver == "legacy":
deprecated_features.add("legacy-resolver")
else:
deprecated_features.discard("legacy-resolver")
else:
features.discard("2020-resolver")
with patch.dict(os.environ, {"PIP_USE_FEATURE": " ".join(features)}):
yield new_resolver
if resolver == "2020-resolver":
features.add("2020-resolver")
else:
features.discard("2020-resolver")

env = {
"PIP_USE_FEATURE": " ".join(features),
"PIP_USE_DEPRECATED": " ".join(deprecated_features),
}
with patch.dict(os.environ, env):
yield resolver


@pytest.fixture(scope='session')
Expand Down
27 changes: 14 additions & 13 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def test_vcs_url_urlquote_normalization(script, tmpdir):
)


@pytest.mark.parametrize("resolver", ["", "--use-feature=2020-resolver"])
@pytest.mark.parametrize("resolver", ["", "--use-deprecated=legacy-resolver"])
def test_basic_install_from_local_directory(
script, data, resolver, with_wheel
):
Expand Down Expand Up @@ -538,7 +538,7 @@ def assert_re_match(pattern, text):


@pytest.mark.network
@pytest.mark.fails_on_new_resolver
@pytest.mark.skip("Fails on new resolver")
def test_hashed_install_failure_later_flag(script, tmpdir):
with requirements_file(
"blessings==1.0\n"
Expand Down Expand Up @@ -941,7 +941,7 @@ def test_install_nonlocal_compatible_wheel(script, data):
def test_install_nonlocal_compatible_wheel_path(
script,
data,
use_new_resolver
resolver_variant,
):
target_dir = script.scratch_path / 'target'

Expand All @@ -952,9 +952,9 @@ def test_install_nonlocal_compatible_wheel_path(
'--no-index',
'--only-binary=:all:',
Path(data.packages) / 'simplewheel-2.0-py3-fakeabi-fakeplat.whl',
expect_error=use_new_resolver
expect_error=(resolver_variant == "2020-resolver"),
)
if use_new_resolver:
if resolver_variant == "2020-resolver":
assert result.returncode == ERROR
else:
assert result.returncode == SUCCESS
Expand Down Expand Up @@ -1456,7 +1456,7 @@ def test_install_no_binary_disables_cached_wheels(script, data, with_wheel):
assert "Running setup.py install for upper" in str(res), str(res)


def test_install_editable_with_wrong_egg_name(script, use_new_resolver):
def test_install_editable_with_wrong_egg_name(script, resolver_variant):
script.scratch_path.joinpath("pkga").mkdir()
pkga_path = script.scratch_path / 'pkga'
pkga_path.joinpath("setup.py").write_text(textwrap.dedent("""
Expand All @@ -1467,12 +1467,12 @@ def test_install_editable_with_wrong_egg_name(script, use_new_resolver):
result = script.pip(
'install', '--editable',
'file://{pkga_path}#egg=pkgb'.format(**locals()),
expect_error=use_new_resolver,
expect_error=(resolver_variant == "2020-resolver"),
)
assert ("Generating metadata for package pkgb produced metadata "
"for project name pkga. Fix your #egg=pkgb "
"fragments.") in result.stderr
if use_new_resolver:
if resolver_variant == "2020-resolver":
assert "has different name in metadata" in result.stderr, str(result)
else:
assert "Successfully installed pkga" in str(result), str(result)
Expand Down Expand Up @@ -1505,7 +1505,7 @@ def test_double_install(script):
assert msg not in result.stderr


def test_double_install_fail(script, use_new_resolver):
def test_double_install_fail(script, resolver_variant):
"""
Test double install failing with two different version requirements
"""
Expand All @@ -1514,9 +1514,9 @@ def test_double_install_fail(script, use_new_resolver):
'pip==7.*',
'pip==7.1.2',
# The new resolver is perfectly capable of handling this
expect_error=(not use_new_resolver)
expect_error=(resolver_variant == "legacy"),
)
if not use_new_resolver:
if resolver_variant == "legacy":
msg = ("Double requirement given: pip==7.1.2 (already in pip==7.*, "
"name='pip')")
assert msg in result.stderr
Expand Down Expand Up @@ -1770,11 +1770,11 @@ def test_user_config_accepted(script):
)
@pytest.mark.parametrize("use_module", [True, False])
def test_install_pip_does_not_modify_pip_when_satisfied(
script, install_args, expected_message, use_module, use_new_resolver):
script, install_args, expected_message, use_module, resolver_variant):
"""
Test it doesn't upgrade the pip if it already satisfies the requirement.
"""
variation = "satisfied" if use_new_resolver else "up-to-date"
variation = "satisfied" if resolver_variant else "up-to-date"
expected_message = expected_message.format(variation)
result = script.pip_install_local(
'pip', *install_args, use_module=use_module
Expand Down Expand Up @@ -1837,6 +1837,7 @@ def test_install_yanked_file_and_print_warning(script, data):
assert 'Successfully installed simple-3.0\n' in result.stdout, str(result)


@skip_if_python2
@pytest.mark.parametrize("install_args", [
(),
("--trusted-host", "localhost"),
Expand Down
3 changes: 3 additions & 0 deletions tests/functional/test_install_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest

from tests.lib import skip_if_python2
from tests.lib.server import (
authorization_response,
file_response,
Expand Down Expand Up @@ -129,6 +130,7 @@ def test_command_line_appends_correctly(script, data):
), 'stdout: {}'.format(result.stdout)


@skip_if_python2
def test_config_file_override_stack(
script, virtualenv, mock_server, shared_data
):
Expand Down Expand Up @@ -247,6 +249,7 @@ def test_prompt_for_authentication(script, data, cert_factory):
result.stdout, str(result)


@skip_if_python2
def test_do_not_prompt_for_authentication(script, data, cert_factory):
"""Test behaviour if --no-input option is given while installing
from a index url requiring authentication
Expand Down
Loading