Repository: pypa/pip

Issue:
Deprecate `--no-binary` implying `setup.py install`
When `--no-binary` is enabled `pip install` does not build a wheel and calls `setup.py install` instead. This happens only for legacy project that don't have a `pyproject.toml`.

This is currently reported as an info-level statement: `Skipping wheel build for %s, due to binaries being disabled for it.`

Since setuptools' `install` command is deprecated, we deprecate this behavior and recommend the `--use-pep517` option (which will become our default at some point).

Towards #8102 

Discussion:

Patch:
diff --git a/news/11451.removal.rst b/news/11451.removal.rst
new file mode 100644
index 000000000..c0d1100ed
--- /dev/null
+++ b/news/11451.removal.rst
@@ -0,0 +1,2 @@
+``--no-binary`` does not imply ``setup.py install`` anymore. Instead a wheel will be
+built locally and installed.
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index f24ef17f9..d25ec9631 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -7,7 +7,6 @@ import site
 from optparse import SUPPRESS_HELP, Values
 from typing import Iterable, List, Optional
 
-from pip._vendor.packaging.utils import canonicalize_name
 from pip._vendor.rich import print_json
 
 from pip._internal.cache import WheelCache
@@ -22,7 +21,6 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS
 from pip._internal.exceptions import CommandError, InstallationError
 from pip._internal.locations import get_scheme
 from pip._internal.metadata import get_environment
-from pip._internal.models.format_control import FormatControl
 from pip._internal.models.installation_report import InstallationReport
 from pip._internal.operations.build.build_tracker import get_build_tracker
 from pip._internal.operations.check import ConflictDetails, check_install_conflicts
@@ -52,26 +50,11 @@ from pip._internal.utils.virtualenv import (
     running_under_virtualenv,
     virtualenv_no_global,
 )
-from pip._internal.wheel_builder import (
-    BdistWheelAllowedPredicate,
-    build,
-    should_build_for_install_command,
-)
+from pip._internal.wheel_builder import build, should_build_for_install_command
 
 logger = getLogger(__name__)
 
 
-def get_check_bdist_wheel_allowed(
-    format_control: FormatControl,
-) -> BdistWheelAllowedPredicate:
-    def check_binary_allowed(req: InstallRequirement) -> bool:
-        canonical_name = canonicalize_name(req.name or "")
-        allowed_formats = format_control.get_allowed_formats(canonical_name)
-        return "binary" in allowed_formats
-
-    return check_binary_allowed
-
-
 class InstallCommand(RequirementCommand):
     """
     Install packages from:
@@ -455,14 +438,10 @@ class InstallCommand(RequirementCommand):
                 modifying_pip = pip_req.satisfied_by is None
             protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
 
-            check_bdist_wheel_allowed = get_check_bdist_wheel_allowed(
-                finder.format_control
-            )
-
             reqs_to_build = [
                 r
                 for r in requirement_set.requirements.values()
-                if should_build_for_install_command(r, check_bdist_wheel_allowed)
+                if should_build_for_install_command(r)
             ]
 
             _, build_failures = build(
diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py
index 18e9be9f3..18d68f3ef 100644
--- a/src/pip/_internal/utils/deprecation.py
+++ b/src/pip/_internal/utils/deprecation.py
@@ -173,16 +173,3 @@ LegacyInstallReasonMissingWheelPackage = LegacyInstallReason(
     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 for projects that "
-        "don't have a 'pyproject.toml' file."
-    ),
-    replacement="to enable the '--use-pep517' option",
-    gone_in="23.1",
-    issue=11451,
-    emit_before_install=True,
-)
diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py
index 15b30af58..612c91ba3 100644
--- a/src/pip/_internal/wheel_builder.py
+++ b/src/pip/_internal/wheel_builder.py
@@ -5,7 +5,7 @@ import logging
 import os.path
 import re
 import shutil
-from typing import Callable, Iterable, List, Optional, Tuple
+from typing import Iterable, List, Optional, Tuple
 
 from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version
 from pip._vendor.packaging.version import InvalidVersion, Version
@@ -19,10 +19,7 @@ from pip._internal.operations.build.wheel import build_wheel_pep517
 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,
-    LegacyInstallReasonNoBinaryForcesSetuptoolsInstall,
-)
+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
@@ -35,7 +32,6 @@ logger = logging.getLogger(__name__)
 
 _egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)
 
-BdistWheelAllowedPredicate = Callable[[InstallRequirement], bool]
 BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]
 
 
@@ -50,7 +46,6 @@ def _contains_egg_info(s: str) -> bool:
 def _should_build(
     req: InstallRequirement,
     need_wheel: bool,
-    check_bdist_wheel: Optional[BdistWheelAllowedPredicate] = None,
 ) -> bool:
     """Return whether an InstallRequirement should be built into a wheel."""
     if req.constraint:
@@ -81,16 +76,6 @@ def _should_build(
     if req.use_pep517:
         return True
 
-    assert check_bdist_wheel is not None
-    if not check_bdist_wheel(req):
-        # /!\ When we change this to unconditionally return True, we must also remove
-        # support for `--install-option`. Indeed, `--install-option` implies
-        # `--no-binary` so we can return False here and run `setup.py install`.
-        # `--global-option` and `--build-option` can remain until we drop support for
-        # building with `setup.py bdist_wheel`.
-        req.legacy_install_reason = LegacyInstallReasonNoBinaryForcesSetuptoolsInstall
-        return False
-
     if not is_wheel_installed():
         # we don't build legacy requirements if wheel is not installed
         req.legacy_install_reason = LegacyInstallReasonMissingWheelPackage
@@ -107,11 +92,8 @@ def should_build_for_wheel_command(
 
 def should_build_for_install_command(
     req: InstallRequirement,
-    check_bdist_wheel_allowed: BdistWheelAllowedPredicate,
 ) -> bool:
-    return _should_build(
-        req, need_wheel=False, check_bdist_wheel=check_bdist_wheel_allowed
-    )
+    return _should_build(req, need_wheel=False)
 
 
 def _should_cache(


Test patch:
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index 65ba15288..94272a63e 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -1665,12 +1665,9 @@ def test_install_no_binary_disables_building_wheels(
     # Wheels are built for local directories, but not cached across runs
     assert "Building wheel for requir" in str(res), str(res)
     # Don't build wheel for upper which was blacklisted
-    assert "Building wheel for upper" not in str(res), str(res)
-    # Wheels are built for local directories, but not cached across runs
-    assert "Running setup.py install for requir" not in str(res), str(res)
+    assert "Building wheel for upper" in str(res), str(res)
     # And these two fell back to sdist based installed.
     assert "Running setup.py install for wheelb" in str(res), str(res)
-    assert "Running setup.py install for upper" in str(res), str(res)
 
 
 @pytest.mark.network
@@ -1720,10 +1717,8 @@ def test_install_no_binary_disables_cached_wheels(
         expect_stderr=True,
     )
     assert "Successfully installed upper-2.0" in str(res), str(res)
-    # No wheel building for upper, which was blacklisted
-    assert "Building wheel for upper" not in str(res), str(res)
-    # Must have used source, not a cached wheel to install upper.
-    assert "Running setup.py install for upper" in str(res), str(res)
+    # upper is built and not obtained from cache
+    assert "Building wheel for upper" in str(res), str(res)
 
 
 def test_install_editable_with_wrong_egg_name(
diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py
index 99e59b97b..cbf52b90b 100644
--- a/tests/functional/test_install_config.py
+++ b/tests/functional/test_install_config.py
@@ -265,10 +265,8 @@ def test_install_no_binary_via_config_disables_cached_wheels(
     finally:
         os.unlink(config_file.name)
     assert "Successfully installed upper-2.0" in str(res), str(res)
-    # No wheel building for upper, which was blacklisted
-    assert "Building wheel for upper" not in str(res), str(res)
-    # Must have used source, not a cached wheel to install upper.
-    assert "Running setup.py install for upper" in str(res), str(res)
+    # upper is built and not obtained from cache
+    assert "Building wheel for upper" in str(res), str(res)
 
 
 @pytest.mark.skipif(
diff --git a/tests/unit/test_wheel_builder.py b/tests/unit/test_wheel_builder.py
index 5444056e7..9c3220536 100644
--- a/tests/unit/test_wheel_builder.py
+++ b/tests/unit/test_wheel_builder.py
@@ -58,63 +58,42 @@ class ReqMock:
 
 
 @pytest.mark.parametrize(
-    "req, disallow_bdist_wheel, expected",
+    "req, expected",
     [
-        # When binaries are allowed, we build.
-        (ReqMock(use_pep517=True), False, True),
-        (ReqMock(use_pep517=False), False, True),
-        # When binaries are disallowed, we don't build, unless pep517 is
-        # enabled.
-        (ReqMock(use_pep517=True), True, True),
-        (ReqMock(use_pep517=False), True, False),
+        # We build, whether pep 517 is enabled or not.
+        (ReqMock(use_pep517=True), True),
+        (ReqMock(use_pep517=False), True),
         # We don't build constraints.
-        (ReqMock(constraint=True), False, False),
+        (ReqMock(constraint=True), False),
         # We don't build reqs that are already wheels.
-        (ReqMock(is_wheel=True), False, False),
-        (ReqMock(editable=True, use_pep517=False), False, False),
+        (ReqMock(is_wheel=True), False),
+        # We build editables if the backend supports PEP 660.
+        (ReqMock(editable=True, use_pep517=False), False),
         (
             ReqMock(editable=True, use_pep517=True, supports_pyproject_editable=True),
-            False,
             True,
         ),
         (
             ReqMock(editable=True, use_pep517=True, supports_pyproject_editable=False),
             False,
-            False,
         ),
-        (ReqMock(source_dir=None), False, False),
+        # We don't build if there is no source dir (whatever that means!).
+        (ReqMock(source_dir=None), False),
         # By default (i.e. when binaries are allowed), VCS requirements
         # should be built in install mode.
         (
             ReqMock(link=Link("git+https://g.c/org/repo"), use_pep517=True),
-            False,
-            True,
-        ),
-        (
-            ReqMock(link=Link("git+https://g.c/org/repo"), use_pep517=False),
-            False,
-            True,
-        ),
-        # Disallowing binaries, however, should cause them not to be built.
-        # unless pep517 is enabled.
-        (
-            ReqMock(link=Link("git+https://g.c/org/repo"), use_pep517=True),
-            True,
             True,
         ),
         (
             ReqMock(link=Link("git+https://g.c/org/repo"), use_pep517=False),
             True,
-            False,
         ),
     ],
 )
-def test_should_build_for_install_command(
-    req: ReqMock, disallow_bdist_wheel: bool, expected: bool
-) -> None:
+def test_should_build_for_install_command(req: ReqMock, expected: bool) -> None:
     should_build = wheel_builder.should_build_for_install_command(
         cast(InstallRequirement, req),
-        check_bdist_wheel_allowed=lambda req: not disallow_bdist_wheel,
     )
     assert should_build is expected
 
@@ -144,7 +123,6 @@ def test_should_build_legacy_wheel_not_installed(is_wheel_installed: mock.Mock)
     legacy_req = ReqMock(use_pep517=False)
     should_build = wheel_builder.should_build_for_install_command(
         cast(InstallRequirement, legacy_req),
-        check_bdist_wheel_allowed=lambda req: True,
     )
     assert not should_build
 
@@ -155,7 +133,6 @@ def test_should_build_legacy_wheel_installed(is_wheel_installed: mock.Mock) -> N
     legacy_req = ReqMock(use_pep517=False)
     should_build = wheel_builder.should_build_for_install_command(
         cast(InstallRequirement, legacy_req),
-        check_bdist_wheel_allowed=lambda req: True,
     )
     assert should_build