diff --git a/news/8559.removal.rst b/news/8559.removal.rst new file mode 100644 index 00000000000..aa9f814120d --- /dev/null +++ b/news/8559.removal.rst @@ -0,0 +1,2 @@ +Deprecate installation with 'setup.py install' when the 'wheel' package is absent for +source distributions without 'pyproject.toml'. diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 29907645c81..65d6362d77c 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -29,6 +29,7 @@ from pip._internal.req import install_given_reqs from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.deprecation import LegacyInstallReasonFailedBdistWheel from pip._internal.utils.distutils_args import parse_distutils_args from pip._internal.utils.filesystem import test_writable_dir from pip._internal.utils.logging import getLogger @@ -440,7 +441,7 @@ def run(self, options: Values, args: List[str]) -> int: # those. for r in build_failures: if not r.use_pep517: - r.legacy_install_reason = 8368 + r.legacy_install_reason = LegacyInstallReasonFailedBdistWheel to_install = resolver.get_installation_order(requirement_set) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index a1e376c893a..88d481dfe5c 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -42,7 +42,7 @@ from pip._internal.operations.install.wheel import install_wheel from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path from pip._internal.req.req_uninstall import UninstallPathSet -from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.deprecation import LegacyInstallReason, deprecated from pip._internal.utils.direct_url_helpers import ( direct_url_for_editable, direct_url_from_link, @@ -96,7 +96,7 @@ def __init__( self.constraint = constraint self.editable = editable self.permit_editable_wheels = permit_editable_wheels - self.legacy_install_reason: Optional[int] = None + self.legacy_install_reason: Optional[LegacyInstallReason] = None # source_dir is the local directory where the linked requirement is # located, or unpacked. In case unpacking is needed, creating and @@ -811,6 +811,11 @@ def install( install_options = list(install_options) + self.install_options try: + if ( + self.legacy_install_reason is not None + and self.legacy_install_reason.emit_before_install + ): + self.legacy_install_reason.emit_deprecation(self.name) success = install_legacy( install_options=install_options, global_options=global_options, @@ -836,18 +841,12 @@ def install( self.install_succeeded = success - if success and self.legacy_install_reason == 8368: - deprecated( - reason=( - "{} was installed using the legacy 'setup.py install' " - "method, because a wheel could not be built for it.".format( - self.name - ) - ), - replacement="to fix the wheel build issue reported above", - gone_in=None, - issue=8368, - ) + if ( + success + and self.legacy_install_reason is not None + and self.legacy_install_reason.emit_after_success + ): + self.legacy_install_reason.emit_deprecation(self.name) def check_invalid_constraint_type(req: InstallRequirement) -> str: diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py index 72bd6f25a55..7c7ace6ff4c 100644 --- a/src/pip/_internal/utils/deprecation.py +++ b/src/pip/_internal/utils/deprecation.py @@ -118,3 +118,58 @@ def deprecated( raise PipDeprecationWarning(message) warnings.warn(message, category=PipDeprecationWarning, stacklevel=2) + + +class LegacyInstallReason: + def __init__( + self, + reason: str, + replacement: Optional[str], + gone_in: Optional[str], + feature_flag: Optional[str] = None, + issue: Optional[int] = None, + emit_after_success: bool = False, + emit_before_install: bool = False, + ): + self._reason = reason + self._replacement = replacement + self._gone_in = gone_in + self._feature_flag = feature_flag + self._issue = issue + self.emit_after_success = emit_after_success + self.emit_before_install = emit_before_install + + def emit_deprecation(self, name: str) -> None: + deprecated( + reason=self._reason.format(name=name), + replacement=self._replacement, + gone_in=self._gone_in, + feature_flag=self._feature_flag, + issue=self._issue, + ) + + +LegacyInstallReasonFailedBdistWheel = LegacyInstallReason( + reason=( + "{name} was installed using the legacy 'setup.py install' " + "method, because a wheel could not be built for it." + ), + replacement="to fix the wheel build issue reported above", + gone_in=None, + issue=8368, + emit_after_success=True, +) + + +LegacyInstallReasonMissingWheelPackage = LegacyInstallReason( + reason=( + "{name} is being installed using the legacy " + "'setup.py install' method, because it does not have a " + "'pyproject.toml' and the 'wheel' package " + "is not installed." + ), + replacement="to enable the '--use-pep517' option", + gone_in=None, + issue=8559, + emit_before_install=True, +) diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 77a17ff0f15..d2a7146edb7 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -19,6 +19,7 @@ from pip._internal.operations.build.wheel_editable import build_wheel_editable from pip._internal.operations.build.wheel_legacy import build_wheel_legacy from pip._internal.req.req_install import InstallRequirement +from pip._internal.utils.deprecation import LegacyInstallReasonMissingWheelPackage from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed from pip._internal.utils.setuptools_build import make_setuptools_clean_args @@ -86,11 +87,7 @@ def _should_build( if not is_wheel_installed(): # we don't build legacy requirements if wheel is not installed - logger.info( - "Using legacy 'setup.py install' for %s, " - "since package 'wheel' is not installed.", - req.name, - ) + req.legacy_install_reason = LegacyInstallReasonMissingWheelPackage return False return True diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index e74477fe299..740cd2f97a6 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -12,6 +12,7 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS from pip._internal.models.index import PyPI, TestPyPI +from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX from pip._internal.utils.misc import rmtree from tests.conftest import CertFactory from tests.lib import ( @@ -2286,3 +2287,32 @@ def test_install_dry_run(script: PipTestEnvironment, data: TestData) -> None: ) assert "Would install simple-3.0" in result.stdout assert "Successfully installed" not in result.stdout + + +def test_install_8559_missing_wheel_package( + script: PipTestEnvironment, shared_data: TestData +) -> None: + result = script.pip( + "install", + "--find-links", + shared_data.find_links, + "simple", + allow_stderr_warning=True, + ) + assert DEPRECATION_MSG_PREFIX in result.stderr + assert "'wheel' package is not installed" in result.stderr + assert "using the legacy 'setup.py install' method" in result.stderr + + +@pytest.mark.usefixtures("with_wheel") +def test_install_8559_wheel_package_present( + script: PipTestEnvironment, shared_data: TestData +) -> None: + result = script.pip( + "install", + "--find-links", + shared_data.find_links, + "simple", + allow_stderr_warning=False, + ) + assert DEPRECATION_MSG_PREFIX not in result.stderr diff --git a/tests/functional/test_install_index.py b/tests/functional/test_install_index.py index 71c0b3e6c60..c1f0ecbd7c6 100644 --- a/tests/functional/test_install_index.py +++ b/tests/functional/test_install_index.py @@ -23,6 +23,7 @@ def test_find_links_relative_path(script: PipTestEnvironment, data: TestData) -> result.did_create(initools_folder) +@pytest.mark.usefixtures("with_wheel") def test_find_links_no_doctype(script: PipTestEnvironment, data: TestData) -> None: shutil.copy(data.packages / "simple-1.0.tar.gz", script.scratch_path) html = script.scratch_path.joinpath("index.html")