diff --git a/news/9769.feature.rst b/news/9769.feature.rst new file mode 100644 index 00000000000..d15f70ca3d4 --- /dev/null +++ b/news/9769.feature.rst @@ -0,0 +1,9 @@ +Deprecate legacy setup.py install when --no-binary is used: + +- add a new feature flag ``--use-feature=always-install-via-wheel``, that is intended to become the default and only mechanism in the future +- when ``--no-binary`` is used without the feature flag, emit a deprecation warning about the fallback to ``setup.py install`` +- when ``--no-binary`` is used with the feature flag, build a wheel from the sdist (via PEP 517 or ``setup.py bdist_wheel``) then install it +- when ``--no-binary`` is used with the feature flag, the wheel that was built locally is cached (unless the cache is disabled) +- since ``--install-option``, ``--build-option`` and ``--global-option`` imply ``--no-binary``, the deprecation warning will be emitted when these options are used without the feature flag +- deprecate ``--install-option`` +- allow using ``--build-option`` in the ``install`` command, as well as in ``requirement.txt`` lines, as a transitory mechanism until the ecosystem supports PEP 517 config settings, which are meant to replace both ``--build-options`` and ``--global-options`` 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/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 47ed92779e9..d81e7bc06d1 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -68,6 +68,12 @@ def check_install_build_global( :param check_options: The options to check, if not supplied defaults to options. """ + if "always-install-via-wheel" in options.features_enabled: + # In this mode, we accept --global-option and --build-option that are + # passed to setup.py bdist_wheel. --install-option is not allowed at + # all in this mode, since setup.py install will not be used. + return + if check_options is None: check_options = options @@ -78,6 +84,7 @@ def getname(n: str) -> Optional[Any]: if any(map(getname, names)): control = options.format_control control.disallow_binaries() + # TODO this should be a deprecation logger.warning( "Disabling all use of wheels due to the use of --build-option " "/ --global-option / --install-option.", @@ -1000,7 +1007,7 @@ 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", "always-install-via-wheel"], 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 29907645c81..0beb2cba85c 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -52,8 +52,12 @@ logger = getLogger(__name__) -def get_check_binary_allowed(format_control: FormatControl) -> BinaryAllowedPredicate: +def get_check_binary_allowed( + format_control: FormatControl, features_enabled: List[str] +) -> BinaryAllowedPredicate: def check_binary_allowed(req: InstallRequirement) -> bool: + if "always-install-via-wheel" in 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 @@ -321,7 +325,12 @@ 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) + if "always-install-via-wheel" in options.features_enabled: + wheel_cache = WheelCache(options.cache_dir) + else: + # TODO when removing this, also remove format control support + # in WheelCache. + wheel_cache = WheelCache(options.cache_dir, options.format_control) build_tracker = self.enter_context(get_build_tracker()) @@ -406,7 +415,9 @@ def run(self, options: Values, args: List[str]) -> int: modifying_pip = pip_req.satisfied_by is None protect_pip_from_modification_on_windows(modifying_pip=modifying_pip) - check_binary_allowed = get_check_binary_allowed(finder.format_control) + check_binary_allowed = get_check_binary_allowed( + finder.format_control, options.features_enabled + ) reqs_to_build = [ r diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index 9dd6c82f210..ee094f91db2 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -105,7 +105,10 @@ def run(self, options: Values, args: List[str]) -> int: session = self.get_default_session(options) finder = self._build_package_finder(options, session) - wheel_cache = WheelCache(options.cache_dir, options.format_control) + if "always-install-via-wheel" in options.features_enabled: + wheel_cache = WheelCache(options.cache_dir) + else: + wheel_cache = WheelCache(options.cache_dir, options.format_control) options.wheel_dir = normalize_path(options.wheel_dir) ensure_dir(options.wheel_dir) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index a1e376c893a..c7e49dc492a 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -800,6 +800,17 @@ def install( self.install_succeeded = True return + if self.legacy_install_reason == 9769: + deprecated( + reason=( + "Installing {} using the legacy 'setup.py install' " + "method, due to binaries being disabled for it.".format(self.name) + ), + replacement="--use-feature=always-install-via-wheel", + gone_in=None, + issue=9769, + ) + # TODO: Why don't we do this for editable installs? # Extend the list of global and install options passed on to diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 77a17ff0f15..781508c93dc 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -78,10 +78,7 @@ def _should_build( return True if not check_binary_allowed(req): - logger.info( - "Skipping wheel build for %s, due to binaries being disabled for it.", - req.name, - ) + req.legacy_install_reason = 9769 return False if not is_wheel_installed():