From 9888b7a816c44b11b5a8f2a4aebe27763d041f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 7 Aug 2022 14:51:21 +0200 Subject: [PATCH 1/5] Deprecate --install-option --- news/11358.removal.rst | 2 ++ src/pip/_internal/cli/cmdoptions.py | 25 ------------- src/pip/_internal/commands/download.py | 7 ++++ src/pip/_internal/commands/install.py | 10 ++++-- src/pip/_internal/commands/wheel.py | 11 ++++-- src/pip/_internal/req/req_file.py | 4 --- src/pip/_internal/req/req_install.py | 50 ++++++++++++++++++++++++++ 7 files changed, 75 insertions(+), 34 deletions(-) create mode 100644 news/11358.removal.rst diff --git a/news/11358.removal.rst b/news/11358.removal.rst new file mode 100644 index 00000000000..9767949b48b --- /dev/null +++ b/news/11358.removal.rst @@ -0,0 +1,2 @@ +Deprecate ``--install-options`` which forces pip to use the deprecated ``install`` +command of ``setuptools``. diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 84e0e783869..5fa86559362 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -59,31 +59,6 @@ def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> Opti return option_group -def check_install_build_global( - options: Values, check_options: Optional[Values] = None -) -> None: - """Disable wheels if per-setup.py call options are set. - - :param options: The OptionParser options to update. - :param check_options: The options to check, if not supplied defaults to - options. - """ - if check_options is None: - check_options = options - - def getname(n: str) -> Optional[Any]: - return getattr(check_options, n, None) - - names = ["build_options", "global_options", "install_options"] - if any(map(getname, names)): - control = options.format_control - control.disallow_binaries() - logger.warning( - "Disabling all use of wheels due to the use of --build-option " - "/ --global-option / --install-option.", - ) - - def check_dist_restriction(options: Values, check_target: bool = False) -> None: """Function for determining if custom platform options are allowed. diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py index 26a5080c790..4132e089883 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -8,6 +8,10 @@ from pip._internal.cli.req_command import RequirementCommand, with_cleanup from pip._internal.cli.status_codes import SUCCESS from pip._internal.operations.build.build_tracker import get_build_tracker +from pip._internal.req.req_install import ( + LegacySetupPyOptionsCheckMode, + check_legacy_setup_py_options, +) from pip._internal.utils.misc import ensure_dir, normalize_path, write_output from pip._internal.utils.temp_dir import TempDirectory @@ -105,6 +109,9 @@ def run(self, options: Values, args: List[str]) -> int: ) reqs = self.get_requirements(args, options, finder, session) + check_legacy_setup_py_options( + options, reqs, LegacySetupPyOptionsCheckMode.DOWNLOAD + ) preparer = self.make_requirement_preparer( temp_build_dir=directory, diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index dcf5ce8c617..b83b2ea2cc0 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -27,7 +27,11 @@ from pip._internal.operations.build.build_tracker import get_build_tracker from pip._internal.operations.check import ConflictDetails, check_install_conflicts from pip._internal.req import install_given_reqs -from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_install import ( + InstallRequirement, + LegacySetupPyOptionsCheckMode, + check_legacy_setup_py_options, +) from pip._internal.utils.compat import WINDOWS from pip._internal.utils.deprecation import LegacyInstallReasonFailedBdistWheel from pip._internal.utils.distutils_args import parse_distutils_args @@ -277,7 +281,6 @@ def run(self, options: Values, args: List[str]) -> int: if options.use_user_site and options.target_dir is not None: raise CommandError("Can not combine '--user' and '--target'") - cmdoptions.check_install_build_global(options) upgrade_strategy = "to-satisfy-only" if options.upgrade: upgrade_strategy = options.upgrade_strategy @@ -338,6 +341,9 @@ def run(self, options: Values, args: List[str]) -> int: try: reqs = self.get_requirements(args, options, finder, session) + check_legacy_setup_py_options( + options, reqs, LegacySetupPyOptionsCheckMode.INSTALL + ) # Only when installing is it permitted to use PEP 660. # In other circumstances (pip wheel, pip download) we generate diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index 9dd6c82f210..501115b60e0 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -10,7 +10,11 @@ from pip._internal.cli.status_codes import SUCCESS from pip._internal.exceptions import CommandError from pip._internal.operations.build.build_tracker import get_build_tracker -from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_install import ( + InstallRequirement, + LegacySetupPyOptionsCheckMode, + check_legacy_setup_py_options, +) from pip._internal.utils.misc import ensure_dir, normalize_path from pip._internal.utils.temp_dir import TempDirectory from pip._internal.wheel_builder import build, should_build_for_wheel_command @@ -100,8 +104,6 @@ def add_options(self) -> None: @with_cleanup def run(self, options: Values, args: List[str]) -> int: - cmdoptions.check_install_build_global(options) - session = self.get_default_session(options) finder = self._build_package_finder(options, session) @@ -119,6 +121,9 @@ def run(self, options: Values, args: List[str]) -> int: ) reqs = self.get_requirements(args, options, finder, session) + check_legacy_setup_py_options( + options, reqs, LegacySetupPyOptionsCheckMode.WHEEL + ) preparer = self.make_requirement_preparer( temp_build_dir=directory, diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index 4550c72d607..eea46af89e8 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -186,10 +186,6 @@ def handle_requirement_line( constraint=line.constraint, ) else: - if options: - # Disable wheels if the user has specified build options - cmdoptions.check_install_build_global(options, line.opts) - # get the options that apply to requirements req_options = {} for dest in SUPPORTED_OPTIONS_REQ_DEST: diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 88d481dfe5c..0cba74fe262 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -8,6 +8,8 @@ import sys import uuid import zipfile +from enum import Enum +from optparse import Values from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union from pip._vendor.packaging.markers import Marker @@ -876,3 +878,51 @@ def check_invalid_constraint_type(req: InstallRequirement) -> str: ) return problem + + +def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool: + if getattr(options, option, None): + return True + for req in reqs: + if getattr(req, option, None): + return True + return False + + +class LegacySetupPyOptionsCheckMode(Enum): + INSTALL = 1 + WHEEL = 2 + DOWNLOAD = 3 + + +def check_legacy_setup_py_options( + options: Values, + reqs: List[InstallRequirement], + mode: LegacySetupPyOptionsCheckMode, +) -> None: + has_install_options = _has_option(options, reqs, "install_options") + has_build_options = _has_option(options, reqs, "build_options") + has_global_options = _has_option(options, reqs, "global_options") + legacy_setup_py_options_present = ( + has_install_options or has_build_options or has_global_options + ) + if not legacy_setup_py_options_present: + return + + options.format_control.disallow_binaries() + logger.warning( + "Implying --no-binary=:all: due to the presence of " + "--build-option / --global-option / --install-option. " + "Consider using --config-settings for more flexibility.", + ) + if mode == LegacySetupPyOptionsCheckMode.INSTALL and has_install_options: + deprecated( + reason=( + "--install-option is deprecated because " + "it forces pip to use the 'setup.py install' " + "command which is itself deprecated." + ), + issue=11358, + replacement="to use --config-settings", + gone_in=None, + ) From d551da7214754388976bff37dfda89bfe2592fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 7 Aug 2022 18:54:11 +0200 Subject: [PATCH 2/5] Fix test_install_requirements_with_options --- tests/unit/test_req_file.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py index 8928fd1690f..4e91e599539 100644 --- a/tests/unit/test_req_file.py +++ b/tests/unit/test_req_file.py @@ -874,5 +874,3 @@ def test_install_requirements_with_options( < args.index("install") < args.index(install_option) ) - assert options.format_control.no_binary == {":all:"} - assert options.format_control.only_binary == set() From ab8d04e8cb9aedf7e805ac56a0f28963704a469e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 7 Aug 2022 16:21:20 +0200 Subject: [PATCH 3/5] Add no-binary-builds-wheels feature flag --- src/pip/_internal/cli/cmdoptions.py | 7 ++++++- src/pip/_internal/commands/install.py | 5 ++++- src/pip/_internal/req/req_install.py | 26 ++++++++++++++++---------- src/pip/_internal/utils/deprecation.py | 16 ++++++++++++++-- src/pip/_internal/wheel_builder.py | 10 +++++----- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 5fa86559362..13ca0c3c517 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -982,7 +982,12 @@ def check_list_path_option(options: Values) -> None: metavar="feature", action="append", default=[], - choices=["2020-resolver", "fast-deps", "truststore"], + choices=[ + "2020-resolver", + "fast-deps", + "truststore", + "no-binary-builds-wheels", + ], help="Enable new functionality, that may be backward incompatible.", ) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index b83b2ea2cc0..d080fe5eb33 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -58,9 +58,12 @@ def get_check_bdist_wheel_allowed( + options: Values, format_control: FormatControl, ) -> BdistWheelAllowedPredicate: def check_binary_allowed(req: InstallRequirement) -> bool: + if "no-binary-builds-wheels" in options.features_enabled: + return True canonical_name = canonicalize_name(req.name or "") allowed_formats = format_control.get_allowed_formats(canonical_name) return "binary" in allowed_formats @@ -418,7 +421,7 @@ def run(self, options: Values, args: List[str]) -> int: protect_pip_from_modification_on_windows(modifying_pip=modifying_pip) check_bdist_wheel_allowed = get_check_bdist_wheel_allowed( - finder.format_control + options, finder.format_control ) reqs_to_build = [ diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 0cba74fe262..b354fc9905b 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -916,13 +916,19 @@ def check_legacy_setup_py_options( "Consider using --config-settings for more flexibility.", ) if mode == LegacySetupPyOptionsCheckMode.INSTALL and has_install_options: - deprecated( - reason=( - "--install-option is deprecated because " - "it forces pip to use the 'setup.py install' " - "command which is itself deprecated." - ), - issue=11358, - replacement="to use --config-settings", - gone_in=None, - ) + if "no-binary-builds-wheels" in options.features_enabled: + logger.warning( + "The deprecated '--install-option' flag is ignored when the " + "'no-binary-builds-wheels' feature is enabled." + ) + else: + deprecated( + reason=( + "--install-option is deprecated because " + "it forces pip to use the 'setup.py install' " + "command which is itself deprecated." + ), + issue=11358, + replacement="to use --config-settings", + gone_in=None, + ) diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py index 7c7ace6ff4c..8e3073850c9 100644 --- a/src/pip/_internal/utils/deprecation.py +++ b/src/pip/_internal/utils/deprecation.py @@ -124,8 +124,8 @@ class LegacyInstallReason: def __init__( self, reason: str, - replacement: Optional[str], - gone_in: Optional[str], + replacement: Optional[str] = None, + gone_in: Optional[str] = None, feature_flag: Optional[str] = None, issue: Optional[int] = None, emit_after_success: bool = False, @@ -173,3 +173,15 @@ def emit_deprecation(self, name: str) -> None: issue=8559, emit_before_install=True, ) + +LegacyInstallReasonNoBinaryForcesSetuptoolsInstall = LegacyInstallReason( + reason=( + "{name} is being installed using the legacy " + "'setup.py install' method, because the '--no-binary' option was enabled " + "for it and this currently disables local wheel building and caching." + ), + feature_flag="no-binary-builds-wheels", + gone_in=None, + issue=99999, # TODO + emit_before_install=True, +) diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 60db28e92c3..6f359421d80 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -19,7 +19,10 @@ 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.deprecation import ( + LegacyInstallReasonMissingWheelPackage, + LegacyInstallReasonNoBinaryForcesSetuptoolsInstall, +) 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 @@ -80,10 +83,7 @@ def _should_build( assert check_bdist_wheel is not None if not check_bdist_wheel(req): - logger.info( - "Skipping wheel build for %s, due to binaries being disabled for it.", - req.name, - ) + req.legacy_install_reason = LegacyInstallReasonNoBinaryForcesSetuptoolsInstall return False if not is_wheel_installed(): From ddd5249add7e54b07c29c89de773da7915e13b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 3 Jan 2021 14:13:45 +0100 Subject: [PATCH 4/5] Pass build_ and global_options to build() in install command In itself, this commit does not change pip's behaviour, because the presence of --build-option and --global-option imply --no-binary :all: which in turn disable wheel building. --- src/pip/_internal/commands/install.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index d080fe5eb33..14aaa9aca84 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -217,6 +217,7 @@ def add_options(self) -> None: self.cmd_opts.add_option(cmdoptions.config_settings()) self.cmd_opts.add_option(cmdoptions.install_options()) + self.cmd_opts.add_option(cmdoptions.build_options()) self.cmd_opts.add_option(cmdoptions.global_options()) self.cmd_opts.add_option( @@ -434,8 +435,8 @@ def run(self, options: Values, args: List[str]) -> int: reqs_to_build, wheel_cache=wheel_cache, verify=True, - build_options=[], - global_options=[], + build_options=options.build_options or [], + global_options=options.global_options or [], ) # If we're using PEP 517, we cannot do a legacy setup.py install From 22f40f6c886a01c96c1d424bc496d1a2ed3ac392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 7 Aug 2022 17:39:33 +0200 Subject: [PATCH 5/5] Deprecate --no-binary disabling the wheel cache --- src/pip/_internal/cache.py | 6 +++++- src/pip/_internal/commands/install.py | 25 ++++++++++++++++++++++--- src/pip/_internal/commands/wheel.py | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py index e51edd5157e..c53b7f023a1 100644 --- a/src/pip/_internal/cache.py +++ b/src/pip/_internal/cache.py @@ -221,7 +221,11 @@ class WheelCache(Cache): when a certain link is not found in the simple wheel cache first. """ - def __init__(self, cache_dir: str, format_control: FormatControl) -> None: + def __init__( + self, cache_dir: str, format_control: Optional[FormatControl] = None + ) -> None: + if format_control is None: + format_control = FormatControl() super().__init__(cache_dir, format_control, {"binary"}) self._wheel_cache = SimpleWheelCache(cache_dir, format_control) self._ephem_cache = EphemWheelCache(format_control) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 14aaa9aca84..dca744347b9 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -33,7 +33,10 @@ check_legacy_setup_py_options, ) from pip._internal.utils.compat import WINDOWS -from pip._internal.utils.deprecation import LegacyInstallReasonFailedBdistWheel +from pip._internal.utils.deprecation import ( + LegacyInstallReasonFailedBdistWheel, + deprecated, +) 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 @@ -333,8 +336,6 @@ def run(self, options: Values, args: List[str]) -> int: target_python=target_python, ignore_requires_python=options.ignore_requires_python, ) - wheel_cache = WheelCache(options.cache_dir, options.format_control) - build_tracker = self.enter_context(get_build_tracker()) directory = TempDirectory( @@ -349,6 +350,24 @@ def run(self, options: Values, args: List[str]) -> int: options, reqs, LegacySetupPyOptionsCheckMode.INSTALL ) + if "no-binary-builds-wheels" in options.features_enabled: + # TODO: remove format_control from WheelCache when the deprecation cycle + # is over + wheel_cache = WheelCache(options.cache_dir) + else: + if options.format_control.no_binary: + deprecated( + reason=( + "--no-binary currently disables reading from " + "the cache of locally built wheels." + ), + replacement="to use the --no-cache-dir option", + feature_flag="no-binary-builds-wheels", + issue=99999, # TODO + gone_in=None, + ) + wheel_cache = WheelCache(options.cache_dir, options.format_control) + # Only when installing is it permitted to use PEP 660. # In other circumstances (pip wheel, pip download) we generate # regular (i.e. non editable) metadata and wheels. diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index 501115b60e0..6702b40bae1 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -15,6 +15,7 @@ LegacySetupPyOptionsCheckMode, check_legacy_setup_py_options, ) +from pip._internal.utils.deprecation import deprecated from pip._internal.utils.misc import ensure_dir, normalize_path from pip._internal.utils.temp_dir import TempDirectory from pip._internal.wheel_builder import build, should_build_for_wheel_command @@ -125,6 +126,24 @@ def run(self, options: Values, args: List[str]) -> int: options, reqs, LegacySetupPyOptionsCheckMode.WHEEL ) + if "no-binary-builds-wheels" in options.features_enabled: + # TODO: remove format_control from WheelCache when the deprecation cycle + # is over + wheel_cache = WheelCache(options.cache_dir) + else: + if options.format_control.no_binary: + deprecated( + reason=( + "--no-binary currently disables reading from " + "the cache of locally built wheels." + ), + replacement="to use the --no-cache-dir option", + feature_flag="no-binary-builds-wheels", + issue=99999, # TODO + gone_in=None, + ) + wheel_cache = WheelCache(options.cache_dir, options.format_control) + preparer = self.make_requirement_preparer( temp_build_dir=directory, options=options,