Repository: pypa/pip
Pull request #11241 (2022-07-08 16:10:59)

Remove `--use-deprecated=backtrack-on-build-failures`
This flag is due for removal in 22.2.

<!---
Thank you for your soon to be pull request. Before you submit this, please
double check to make sure that you've added a news file fragment. In pip we
generate our NEWS.rst from multiple news fragment files, and all pull requests
require either a news file fragment or a marker to indicate they don't require
one.

To read more about adding a news file fragment for your PR, please check out
our documentation at: https://pip.pypa.io/en/latest/development/contributing/#news-entries
-->


sbidoul: @pradyunsg CI is complaining here.

Patch:
diff --git a/docs/html/topics/vcs-support.md b/docs/html/topics/vcs-support.md
index 70bb5beb9..538b4a791 100644
--- a/docs/html/topics/vcs-support.md
+++ b/docs/html/topics/vcs-support.md
@@ -73,16 +73,16 @@ The supported schemes are `svn`, `svn+svn`, `svn+http`, `svn+https` and
 `svn+ssh`. Here are some of the supported forms:
 
 ```none
-MyProject @ svn+https://svn.example.com/MyProject
-MyProject @ svn+ssh://svn.example.com/MyProject
-MyProject @ svn+ssh://user@svn.example.com/MyProject
+MyProject @svn+https://svn.example.com/MyProject
+MyProject @svn+ssh://svn.example.com/MyProject
+MyProject @svn+ssh://user@svn.example.com/MyProject
 ```
 
 You can also give specific revisions to an SVN URL, like so:
 
 ```none
--e svn+http://svn.example.com/svn/MyProject/trunk@2019#egg=MyProject
--e svn+http://svn.example.com/svn/MyProject/trunk@{20080101}#egg=MyProject
+MyProject @ -e svn+http://svn.example.com/svn/MyProject/trunk@2019
+MyProject @ -e svn+http://svn.example.com/svn/MyProject/trunk@{20080101}
 ```
 
 Note that you need to use [Editable VCS installs](#editable-vcs-installs) for
diff --git a/news/10222.bugfix.rst b/news/10222.bugfix.rst
deleted file mode 100644
index 0595e2b87..000000000
--- a/news/10222.bugfix.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Fix ``pip install --pre`` for packages with pre-release build dependencies defined both in ``pyproject.toml``'s ``build-system.requires`` and ``setup.py``'s ``setup_requires``.
diff --git a/news/11225.feature.rst b/news/11225.feature.rst
deleted file mode 100644
index 2da8f5617..000000000
--- a/news/11225.feature.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-pip's deprecation warnings now subclass the built-in ``DeprecationWarning``, and
-can be suppressed by running the Python interpreter with
-``-W ignore::DeprecationWarning``.
diff --git a/news/11241.removal.rst b/news/11241.removal.rst
new file mode 100644
index 000000000..69fb01866
--- /dev/null
+++ b/news/11241.removal.rst
@@ -0,0 +1,1 @@
+Remove ``--use-deprecated=backtrack-on-build-failures``.
diff --git a/news/11248.bugfix.rst b/news/11248.bugfix.rst
deleted file mode 100644
index c48146973..000000000
--- a/news/11248.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Use ``importlib.resources`` to read the ``vendor.txt`` file in ``pip debug``.
-This makes the command safe for use from a zipapp.
diff --git a/noxfile.py b/noxfile.py
index b7d3d8253..9de795fe5 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -229,23 +229,15 @@ def vendoring(session: nox.Session) -> None:
 
 @nox.session
 def coverage(session: nox.Session) -> None:
-    # Install source distribution
-    run_with_protected_pip(session, "install", ".")
-
-    # Install test dependencies
-    run_with_protected_pip(session, "install", "-r", REQUIREMENTS["tests"])
-
-    if not os.path.exists(".coverage-output"):
-        os.mkdir(".coverage-output")
+    if not os.path.exists("./.coverage-output"):
+        os.mkdir("./.coverage-output")
     session.run(
         "pytest",
         "--cov=pip",
         "--cov-config=./setup.cfg",
-        *session.posargs,
         env={
             "COVERAGE_OUTPUT_DIR": "./.coverage-output",
-            "COVERAGE_PROCESS_START": os.fsdecode(Path("setup.cfg").resolve()),
-            "SETUPTOOLS_USE_DISTUTILS": "stdlib",
+            "COVERAGE_PROCESS_START": "./setup.cfg",
         },
     )
 
diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py
index df5152a78..35ae1e68c 100644
--- a/src/pip/_internal/cli/cmdoptions.py
+++ b/src/pip/_internal/cli/cmdoptions.py
@@ -1013,7 +1013,6 @@ use_deprecated_feature: Callable[..., Option] = partial(
     default=[],
     choices=[
         "legacy-resolver",
-        "backtrack-on-build-failures",
         "html5lib",
     ],
     help=("Enable deprecated functionality, that will be removed in the future."),
diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py
index 6db058869..b289426cf 100644
--- a/src/pip/_internal/cli/req_command.py
+++ b/src/pip/_internal/cli/req_command.py
@@ -34,7 +34,6 @@ from pip._internal.req.req_file import parse_requirements
 from pip._internal.req.req_install import InstallRequirement
 from pip._internal.resolution.base import BaseResolver
 from pip._internal.self_outdated_check import pip_self_version_check
-from pip._internal.utils.deprecation import deprecated
 from pip._internal.utils.temp_dir import (
     TempDirectory,
     TempDirectoryTypeRegistry,
@@ -270,31 +269,6 @@ class RequirementCommand(IndexGroupCommand):
 
         return "2020-resolver"
 
-    @staticmethod
-    def determine_build_failure_suppression(options: Values) -> bool:
-        """Determines whether build failures should be suppressed and backtracked on."""
-        if "backtrack-on-build-failures" not in options.deprecated_features_enabled:
-            return False
-
-        if "legacy-resolver" in options.deprecated_features_enabled:
-            raise CommandError("Cannot backtrack with legacy resolver.")
-
-        deprecated(
-            reason=(
-                "Backtracking on build failures can mask issues related to how "
-                "a package generates metadata or builds a wheel. This flag will "
-                "be removed in pip 22.2."
-            ),
-            gone_in=None,
-            replacement=(
-                "avoiding known-bad versions by explicitly telling pip to ignore them "
-                "(either directly as requirements, or via a constraints file)"
-            ),
-            feature_flag=None,
-            issue=10655,
-        )
-        return True
-
     @classmethod
     def make_requirement_preparer(
         cls,
@@ -371,7 +345,6 @@ class RequirementCommand(IndexGroupCommand):
             use_pep517=use_pep517,
             config_settings=getattr(options, "config_settings", None),
         )
-        suppress_build_failures = cls.determine_build_failure_suppression(options)
         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
@@ -391,7 +364,6 @@ class RequirementCommand(IndexGroupCommand):
                 force_reinstall=force_reinstall,
                 upgrade_strategy=upgrade_strategy,
                 py_version_info=py_version_info,
-                suppress_build_failures=suppress_build_failures,
             )
         import pip._internal.resolution.legacy.resolver
 
diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py
index 6fad1fe89..084d7fa98 100644
--- a/src/pip/_internal/commands/debug.py
+++ b/src/pip/_internal/commands/debug.py
@@ -1,4 +1,3 @@
-import importlib.resources
 import locale
 import logging
 import os
@@ -11,6 +10,7 @@ import pip._vendor
 from pip._vendor.certifi import where
 from pip._vendor.packaging.version import parse as parse_version
 
+from pip import __file__ as pip_location
 from pip._internal.cli import cmdoptions
 from pip._internal.cli.base_command import Command
 from pip._internal.cli.cmdoptions import make_target_python
@@ -35,7 +35,11 @@ def show_sys_implementation() -> None:
 
 
 def create_vendor_txt_map() -> Dict[str, str]:
-    with importlib.resources.open_text("pip._vendor", "vendor.txt") as f:
+    vendor_txt_path = os.path.join(
+        os.path.dirname(pip_location), "_vendor", "vendor.txt"
+    )
+
+    with open(vendor_txt_path) as f:
         # Purge non version specifying lines.
         # Also, remove any space prefix or suffixes (including comments).
         lines = [
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index 4569033db..a4c24b52a 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -27,7 +27,6 @@ from pip._internal.cache import CacheEntry, WheelCache
 from pip._internal.exceptions import (
     DistributionNotFound,
     InstallationError,
-    InstallationSubprocessError,
     MetadataInconsistent,
     UnsupportedPythonVersion,
     UnsupportedWheel,
@@ -97,7 +96,6 @@ class Factory:
         force_reinstall: bool,
         ignore_installed: bool,
         ignore_requires_python: bool,
-        suppress_build_failures: bool,
         py_version_info: Optional[Tuple[int, ...]] = None,
     ) -> None:
         self._finder = finder
@@ -108,7 +106,6 @@ class Factory:
         self._use_user_site = use_user_site
         self._force_reinstall = force_reinstall
         self._ignore_requires_python = ignore_requires_python
-        self._suppress_build_failures = suppress_build_failures
 
         self._build_failures: Cache[InstallationError] = {}
         self._link_candidate_cache: Cache[LinkCandidate] = {}
@@ -201,12 +198,6 @@ class Factory:
                     )
                     self._build_failures[link] = e
                     return None
-                except InstallationSubprocessError as e:
-                    if not self._suppress_build_failures:
-                        raise
-                    logger.warning("Discarding %s due to build failure: %s", link, e)
-                    self._build_failures[link] = e
-                    return None
 
             base: BaseCandidate = self._editable_candidate_cache[link]
         else:
@@ -228,12 +219,6 @@ class Factory:
                     )
                     self._build_failures[link] = e
                     return None
-                except InstallationSubprocessError as e:
-                    if not self._suppress_build_failures:
-                        raise
-                    logger.warning("Discarding %s due to build failure: %s", link, e)
-                    self._build_failures[link] = e
-                    return None
             base = self._link_candidate_cache[link]
 
         if not extras:
diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py
index 32ef7899b..a605d6c25 100644
--- a/src/pip/_internal/resolution/resolvelib/resolver.py
+++ b/src/pip/_internal/resolution/resolvelib/resolver.py
@@ -47,7 +47,6 @@ class Resolver(BaseResolver):
         ignore_requires_python: bool,
         force_reinstall: bool,
         upgrade_strategy: str,
-        suppress_build_failures: bool,
         py_version_info: Optional[Tuple[int, ...]] = None,
     ):
         super().__init__()
@@ -62,7 +61,6 @@ class Resolver(BaseResolver):
             force_reinstall=force_reinstall,
             ignore_installed=ignore_installed,
             ignore_requires_python=ignore_requires_python,
-            suppress_build_failures=suppress_build_failures,
             py_version_info=py_version_info,
         )
         self.ignore_dependencies = ignore_dependencies
diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py
index 7964095ff..72bd6f25a 100644
--- a/src/pip/_internal/utils/deprecation.py
+++ b/src/pip/_internal/utils/deprecation.py
@@ -13,7 +13,7 @@ from pip import __version__ as current_version  # NOTE: tests patch this name.
 DEPRECATION_MSG_PREFIX = "DEPRECATION: "
 
 
-class PipDeprecationWarning(DeprecationWarning):
+class PipDeprecationWarning(Warning):
     pass
 
 
diff --git a/src/pip/_internal/utils/distutils_args.py b/src/pip/_internal/utils/distutils_args.py
index 2fd186207..e4aa5b827 100644
--- a/src/pip/_internal/utils/distutils_args.py
+++ b/src/pip/_internal/utils/distutils_args.py
@@ -1,43 +1,42 @@
-from getopt import GetoptError, getopt
+from distutils.errors import DistutilsArgError
+from distutils.fancy_getopt import FancyGetopt
 from typing import Dict, List
 
 _options = [
-    "exec-prefix=",
-    "home=",
-    "install-base=",
-    "install-data=",
-    "install-headers=",
-    "install-lib=",
-    "install-platlib=",
-    "install-purelib=",
-    "install-scripts=",
-    "prefix=",
-    "root=",
-    "user",
+    ("exec-prefix=", None, ""),
+    ("home=", None, ""),
+    ("install-base=", None, ""),
+    ("install-data=", None, ""),
+    ("install-headers=", None, ""),
+    ("install-lib=", None, ""),
+    ("install-platlib=", None, ""),
+    ("install-purelib=", None, ""),
+    ("install-scripts=", None, ""),
+    ("prefix=", None, ""),
+    ("root=", None, ""),
+    ("user", None, ""),
 ]
 
 
+# typeshed doesn't permit Tuple[str, None, str], see python/typeshed#3469.
+_distutils_getopt = FancyGetopt(_options)  # type: ignore
+
+
 def parse_distutils_args(args: List[str]) -> Dict[str, str]:
-    """Parse provided arguments, returning an object that has the matched arguments.
+    """Parse provided arguments, returning an object that has the
+    matched arguments.
 
     Any unknown arguments are ignored.
     """
     result = {}
     for arg in args:
         try:
-            parsed_opt, _ = getopt(args=[arg], shortopts="", longopts=_options)
-        except GetoptError:
+            _, match = _distutils_getopt.getopt(args=[arg])
+        except DistutilsArgError:
             # We don't care about any other options, which here may be
             # considered unrecognized since our option list is not
             # exhaustive.
-            continue
-
-        if not parsed_opt:
-            continue
-
-        option = parsed_opt[0]
-        name_from_parsed = option[0][2:].replace("-", "_")
-        value_from_parsed = option[1] or "true"
-        result[name_from_parsed] = value_from_parsed
-
+            pass
+        else:
+            result.update(match.__dict__)
     return result


Test patch:
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index e74477fe2..ad2b4f5e9 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -733,45 +733,6 @@ def test_editable_install__local_dir_setup_requires_with_pyproject(
     script.pip("install", "--find-links", shared_data.find_links, "-e", local_dir)
 
 
-def test_install_pre__setup_requires_with_pyproject(
-    script: PipTestEnvironment, shared_data: TestData, common_wheels: Path
-) -> None:
-    """
-    Test installing with a pre-release build dependency declared in both
-    setup.py and pyproject.toml.
-
-    https://github.com/pypa/pip/issues/10573
-    """
-    depends_package = "prerelease_dependency"
-    depends_path = create_basic_wheel_for_package(script, depends_package, "1.0.0a1")
-
-    local_dir = script.scratch_path.joinpath("temp")
-    local_dir.mkdir()
-    pyproject_path = local_dir.joinpath("pyproject.toml")
-    pyproject_path.write_text(
-        "[build-system]\n"
-        f'requires = ["setuptools", "wheel", "{depends_package}"]\n'
-        'build-backend = "setuptools.build_meta"\n'
-    )
-    setup_py_path = local_dir.joinpath("setup.py")
-    setup_py_path.write_text(
-        "from setuptools import setup\n"
-        f"setup(name='dummy', setup_requires=['{depends_package}'])\n"
-    )
-
-    script.pip(
-        "install",
-        "--pre",
-        "--no-cache-dir",
-        "--no-index",
-        "--find-links",
-        common_wheels,
-        "--find-links",
-        depends_path.parent,
-        local_dir,
-    )
-
-
 @pytest.mark.network
 def test_upgrade_argparse_shadowed(script: PipTestEnvironment) -> None:
     # If argparse is installed - even if shadowed for imported - we support
diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py
index f3acdb9ca..efcae2928 100644
--- a/tests/functional/test_new_resolver.py
+++ b/tests/functional/test_new_resolver.py
@@ -2326,26 +2326,6 @@ def test_new_resolver_do_not_backtrack_on_build_failure(
     assert "egg_info" in result.stderr
 
 
-def test_new_resolver_flag_permits_backtracking_on_build_failure(
-    script: PipTestEnvironment,
-) -> None:
-    create_basic_sdist_for_package(script, "pkg1", "2.0", fails_egg_info=True)
-    create_basic_wheel_for_package(script, "pkg1", "1.0")
-
-    script.pip(
-        "install",
-        "--use-deprecated=backtrack-on-build-failures",
-        "--no-cache-dir",
-        "--no-index",
-        "--find-links",
-        script.scratch_path,
-        "pkg1",
-        allow_stderr_warning=True,
-    )
-
-    script.assert_installed(pkg1="1.0")
-
-
 def test_new_resolver_works_when_failing_package_builds_are_disallowed(
     script: PipTestEnvironment,
 ) -> None:
diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py
index 6cef3c17c..a8889293b 100644
--- a/tests/unit/resolution_resolvelib/conftest.py
+++ b/tests/unit/resolution_resolvelib/conftest.py
@@ -63,7 +63,6 @@ def factory(finder: PackageFinder, preparer: RequirementPreparer) -> Iterator[Fa
         force_reinstall=False,
         ignore_installed=False,
         ignore_requires_python=False,
-        suppress_build_failures=False,
         py_version_info=None,
     )
 
diff --git a/tests/unit/resolution_resolvelib/test_resolver.py b/tests/unit/resolution_resolvelib/test_resolver.py
index db71f911a..87c2b5f35 100644
--- a/tests/unit/resolution_resolvelib/test_resolver.py
+++ b/tests/unit/resolution_resolvelib/test_resolver.py
@@ -29,7 +29,6 @@ def resolver(preparer: RequirementPreparer, finder: PackageFinder) -> Resolver:
         ignore_requires_python=False,
         force_reinstall=False,
         upgrade_strategy="to-satisfy-only",
-        suppress_build_failures=False,
     )
     return resolver
 
diff --git a/tests/unit/test_utils_distutils_args.py b/tests/unit/test_utils_distutils_args.py
index 21f31e926..e63c565a1 100644
--- a/tests/unit/test_utils_distutils_args.py
+++ b/tests/unit/test_utils_distutils_args.py
@@ -60,4 +60,4 @@ def test_all_value_options_work(name: str, value: str) -> None:
 
 def test_user_option_works() -> None:
     result = parse_distutils_args(["--user"])
-    assert result["user"]
+    assert result["user"] == 1