Repository: pypa/pip (python)

Issue:
regression: `--no-build-isolation` validation causes failure with setup deps installed from custom commit
### Description

After updating from pip `22.0` to `22.1`, `--no-build-isolation` is incompatible with packages installed from a custom commit.
We have setup code that depends on ctypesgen, which did not get a PyPI update for 3 years, while git main is improved regularly and considered stable. Therefore, we install ctypesgen from the latest sources, and then pass `--no-build-isolation` so that the newer ctypesgen is used, not a PyPI release downloaded into a venv. Since pip `22.1`, this fails with the following error:
```
ERROR: Some build dependencies for file:///home/mara/projects/pypdfium2 conflict with the backend dependencies: ctypesgen==1.0.3.dev98+g2120dbf is incompatible with ctypesgen.
```
This makes it impossible to install a package via `pip3 install` while using an unreleased version of a setup dependency.
I am aware that dependency validation was added on purpose, but this particular behaviour is neither justified nor desirable.


### Expected behavior

`ctypesgen==1.0.3.dev98+g2120dbf` fulfils the requirement `ctypesgen` defined in `pyproject.toml:build-system:requires`. It should thus not cause an error.

Commit in question: https://github.com/pypa/pip/commit/0c6d20f8c70a561fe7d13bd3baf3c82eb2cd2b0e

### pip version

22.1

### Python version

3.8.10

### OS

Ubuntu 20.04 (Linux)

### How to Reproduce

```bash
git clone "https://github.com/ctypesgen/ctypesgen.git"
cd ctypesgen
pip3 install .
cd ..
python3 -m pip install pip==22.1
git clone "https://github.com/pypdfium2-team/pypdfium2.git"
cd pypdfium2
pip3 install . -v --no-build-isolation
cd ..
rm -rf ctypesgen pypdfium2
```

### Output

```sh-session
mara@ub2020:/tmp$ git clone "https://github.com/ctypesgen/ctypesgen.git"
Klone nach 'ctypesgen' …
remote: Enumerating objects: 2014, done.
remote: Counting objects: 100% (199/199), done.
remote: Compressing objects: 100% (54/54), done.
remote: Total 2014 (delta 157), reused 145 (delta 145), pack-reused 1815
Empfange Objekte: 100% (2014/2014), 1.11 MiB | 6.06 MiB/s, fertig.
Löse Unterschiede auf: 100% (1295/1295), fertig.
mara@ub2020:/tmp$ cd ctypesgen
mara@ub2020:/tmp/ctypesgen$ pip3 install .
Defaulting to user installation because normal site-packages is not writeable
Processing /tmp/ctypesgen
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Installing backend dependencies ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: ctypesgen
  Building wheel for ctypesgen (pyproject.toml) ... done
  Created wheel for ctypesgen: filename=ctypesgen-1.0.3.dev98+g2120dbf-py3-none-any.whl size=124318 sha256=b456fcde59ffd9a4b32308b9f9edd79d44dba1a0951b2423b28a66a7b6c8d15f
  Stored in directory: /tmp/pip-ephem-wheel-cache-bahnw45i/wheels/ea/0e/da/227e0ec876339d2ee5e1ec3bf0476fd4eb2076109ca8d08ee0
Successfully built ctypesgen
Installing collected packages: ctypesgen
  Attempting uninstall: ctypesgen
    Found existing installation: ctypesgen 1.0.3.dev98+g2120dbf
    Uninstalling ctypesgen-1.0.3.dev98+g2120dbf:
      Successfully uninstalled ctypesgen-1.0.3.dev98+g2120dbf
Successfully installed ctypesgen-1.0.3.dev98+g2120dbf
mara@ub2020:/tmp/ctypesgen$ cd ..
mara@ub2020:/tmp$ python3 -m pip install pip==22.1
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: pip==22.1 in /home/mara/.local/lib/python3.8/site-packages (22.1)
mara@ub2020:/tmp$ git clone "https://github.com/pypdfium2-team/pypdfium2.git"
Klone nach 'pypdfium2' …
remote: Enumerating objects: 5138, done.
remote: Counting objects: 100% (2084/2084), done.
remote: Compressing objects: 100% (589/589), done.
remote: Total 5138 (delta 1542), reused 1824 (delta 1390), pack-reused 3054
Empfange Objekte: 100% (5138/5138), 1.19 MiB | 2.95 MiB/s, fertig.
Löse Unterschiede auf: 100% (3415/3415), fertig.
mara@ub2020:/tmp$ cd pypdfium2
mara@ub2020:/tmp/pypdfium2$ pip3 install . -v --no-build-isolation
Using pip 22.1 from /home/mara/.local/lib/python3.8/site-packages/pip (python 3.8)
Defaulting to user installation because normal site-packages is not writeable
Processing /tmp/pypdfium2
ERROR: Some build dependencies for file:///tmp/pypdfium2 conflict with the backend dependencies: ctypesgen==1.0.3.dev98+g2120dbf is incompatible with ctypesgen.
mara@ub2020:/tmp/pypdfium2$ cd ..
mara@ub2020:/tmp$ rm -rf ctypesgen pypdfium2
mara@ub2020:/tmp$
```


### Code of Conduct

- [X] I agree to follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/).

Pull request description:
Allow pre-release to satisfy build requirements
This bug has been there fore since forever, but only exposed when we actually trying to enforce it to manually populated environments.

Fix #11123.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 2a6921300..c691a0058 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -61,12 +61,13 @@ body:
       description: >-
         Provide the output of the steps above, including the commands
         themselves and pip's output/traceback etc. If you're familiar with
-        Markdown, DO NOT add backticks. They're added automatically.
+        Markdown, this block will have triple backticks added automatically
+        around it -- you don't have to add them.
 
-        If you want to present output from multiple commands, please prefix
-        the line containing the command with `$ `. Please also ensure that
-        the "How to reproduce" section contains matching instructions for
-        reproducing this.
+        If you want to present output from multiple commands, please present
+        that as a shell session (commands you run get prefixed with `$ `).
+        Please also ensure that the "How to reproduce" section contains matching
+        instructions for reproducing this.
       render: sh-session
 
   - type: checkboxes
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e467b3e50..f0b8ba644 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -219,12 +219,11 @@ jobs:
         env:
           TEMP: "R:\\Temp"
 
-  # TODO: Remove this when we add Python 3.11 to CI.
   tests-importlib-metadata:
     name: tests for importlib.metadata backend
     runs-on: ubuntu-latest
     env:
-      _PIP_USE_IMPORTLIB_METADATA: 'true'
+      _PIP_METADATA_BACKEND_IMPORTLIB: egg-compat
 
     needs: [pre-commit, packaging, determine-changes]
     if: >-
@@ -242,6 +241,7 @@ jobs:
 
       - run: pip install nox 'virtualenv<20'
 
+      # Main check
       - name: Run unit tests
         run: >-
           nox -s test-3.10 --
diff --git a/AUTHORS.txt b/AUTHORS.txt
index ac4dd1337..5b33615ab 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -402,7 +402,6 @@ Masklinn
 Matej Stuchlik
 Mathew Jennings
 Mathieu Bridon
-Mathieu Kniewallner
 Matt Bacchi
 Matt Good
 Matt Maker
diff --git a/NEWS.rst b/NEWS.rst
index b63997c0b..961768250 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -9,25 +9,6 @@
 
 .. towncrier release notes start
 
-22.1 (2022-05-11)
-=================
-
-Process
--------
-
-- Enable the ``importlib.metadata`` metadata implementation by default on
-  Python 3.11 (or later). The environment variable ``_PIP_USE_IMPORTLIB_METADATA``
-  can still be used to enable the implementation on 3.10 and earlier, or disable
-  it on 3.11 (by setting it to ``0`` or ``false``).
-
-Bug Fixes
----------
-
-- Revert `#9243 <https://github.com/pypa/pip/issues/9243>`_ since it introduced a regression in certain edge cases. (`#10962 <https://github.com/pypa/pip/issues/10962>`_)
-- Fix missing ``REQUESTED`` metadata when using URL constraints. (`#11079 <https://github.com/pypa/pip/issues/11079>`_)
-- ``pip config`` now normalizes names by converting underscores into dashes. (`#9330 <https://github.com/pypa/pip/issues/9330>`_)
-
-
 22.1b1 (2022-04-30)
 ===================
 
diff --git a/news/10962.bugfix.rst b/news/10962.bugfix.rst
new file mode 100644
index 000000000..8ff4b4283
--- /dev/null
+++ b/news/10962.bugfix.rst
@@ -0,0 +1,1 @@
+Revert `#9243 <https://github.com/pypa/pip/issues/9243>`_ since it introduced a regression in certain edge cases.
diff --git a/news/11079.bugfix.rst b/news/11079.bugfix.rst
new file mode 100644
index 000000000..61305a765
--- /dev/null
+++ b/news/11079.bugfix.rst
@@ -0,0 +1,1 @@
+Fix missing ``REQUESTED`` metadata when using URL constraints.
diff --git a/news/11112.bugfix.rst b/news/11112.bugfix.rst
deleted file mode 100644
index 87b4b56a4..000000000
--- a/news/11112.bugfix.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Properly filter out optional dependencies (i.e. extras) when checking build environment distributions.
diff --git a/news/11116.bugfix.rst b/news/11116.bugfix.rst
deleted file mode 100644
index c0cdbd4ee..000000000
--- a/news/11116.bugfix.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Change the build environment dependency checking to be opt-in.
diff --git a/news/11123.bugfix.rst b/news/11123.bugfix.rst
new file mode 100644
index 000000000..17d3de27e
--- /dev/null
+++ b/news/11123.bugfix.rst
@@ -0,0 +1,3 @@
+Allow using a pre-release version to satisfy a build requirement. This helps
+manually populated build environments to more accurately detect build-time
+requirement conflicts.
diff --git a/news/9330.bugfix.rst b/news/9330.bugfix.rst
new file mode 100644
index 000000000..63e44c238
--- /dev/null
+++ b/news/9330.bugfix.rst
@@ -0,0 +1,1 @@
+``pip config`` now normalizes names by converting underscores into dashes.
diff --git a/src/pip/__init__.py b/src/pip/__init__.py
index 9cd8466e4..c44b971cc 100644
--- a/src/pip/__init__.py
+++ b/src/pip/__init__.py
@@ -1,6 +1,6 @@
 from typing import List, Optional
 
-__version__ = "22.2.dev0"
+__version__ = "22.1.dev1"
 
 
 def main(args: Optional[List[str]] = None) -> int:
diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py
index 01165113b..9bf317547 100644
--- a/src/pip/_internal/build_env.py
+++ b/src/pip/_internal/build_env.py
@@ -175,10 +175,8 @@ class BuildEnvironment:
             )
             for req_str in reqs:
                 req = Requirement(req_str)
-                # We're explicitly evaluating with an empty extra value, since build
-                # environments are not provided any mechanism to select specific extras.
-                if req.marker is not None and not req.marker.evaluate({"extra": ""}):
-                    continue
+                if req.marker is not None and not req.marker.evaluate():
+                    continue  # FIXME: Consider extras?
                 dist = env.get_distribution(req.name)
                 if not dist:
                     missing.add(req_str)
@@ -187,7 +185,7 @@ class BuildEnvironment:
                     installed_req_str = f"{req.name}=={dist.version}"
                 else:
                     installed_req_str = f"{req.name}==={dist.version}"
-                if dist.version not in req.specifier:
+                if not req.specifier.contains(dist.version, prereleases=True):
                     conflicting.add((installed_req_str, req_str))
                 # FIXME: Consider direct URL?
         return conflicting, missing
diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py
index e96b8e5e3..c84ecabd3 100644
--- a/src/pip/_internal/cli/cmdoptions.py
+++ b/src/pip/_internal/cli/cmdoptions.py
@@ -749,15 +749,6 @@ no_build_isolation: Callable[..., Option] = partial(
     "if this option is used.",
 )
 
-check_build_deps: Callable[..., Option] = partial(
-    Option,
-    "--check-build-dependencies",
-    dest="check_build_deps",
-    action="store_true",
-    default=False,
-    help="Check the build dependencies when PEP517 is used.",
-)
-
 
 def _handle_no_use_pep517(
     option: Option, opt: str, value: str, parser: OptionParser
diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py
index 539d21dff..aab177002 100644
--- a/src/pip/_internal/cli/req_command.py
+++ b/src/pip/_internal/cli/req_command.py
@@ -293,7 +293,6 @@ class RequirementCommand(IndexGroupCommand):
             src_dir=options.src_dir,
             download_dir=download_dir,
             build_isolation=options.build_isolation,
-            check_build_deps=options.check_build_deps,
             build_tracker=build_tracker,
             session=session,
             progress_bar=options.progress_bar,
diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py
index d70ce4f1f..a6d7e628f 100644
--- a/src/pip/_internal/commands/download.py
+++ b/src/pip/_internal/commands/download.py
@@ -49,7 +49,6 @@ class DownloadCommand(RequirementCommand):
         self.cmd_opts.add_option(cmdoptions.no_build_isolation())
         self.cmd_opts.add_option(cmdoptions.use_pep517())
         self.cmd_opts.add_option(cmdoptions.no_use_pep517())
-        self.cmd_opts.add_option(cmdoptions.check_build_deps())
         self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
 
         self.cmd_opts.add_option(
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index 3634ea04c..55edb280c 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -189,7 +189,6 @@ class InstallCommand(RequirementCommand):
         self.cmd_opts.add_option(cmdoptions.no_build_isolation())
         self.cmd_opts.add_option(cmdoptions.use_pep517())
         self.cmd_opts.add_option(cmdoptions.no_use_pep517())
-        self.cmd_opts.add_option(cmdoptions.check_build_deps())
 
         self.cmd_opts.add_option(cmdoptions.config_settings())
         self.cmd_opts.add_option(cmdoptions.install_options())
diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py
index 9dd6c82f2..05b91d473 100644
--- a/src/pip/_internal/commands/wheel.py
+++ b/src/pip/_internal/commands/wheel.py
@@ -57,7 +57,6 @@ class WheelCommand(RequirementCommand):
         self.cmd_opts.add_option(cmdoptions.no_build_isolation())
         self.cmd_opts.add_option(cmdoptions.use_pep517())
         self.cmd_opts.add_option(cmdoptions.no_use_pep517())
-        self.cmd_opts.add_option(cmdoptions.check_build_deps())
         self.cmd_opts.add_option(cmdoptions.constraints())
         self.cmd_opts.add_option(cmdoptions.editable())
         self.cmd_opts.add_option(cmdoptions.requirements())
diff --git a/src/pip/_internal/distributions/base.py b/src/pip/_internal/distributions/base.py
index 75ce2dc90..149fff55d 100644
--- a/src/pip/_internal/distributions/base.py
+++ b/src/pip/_internal/distributions/base.py
@@ -31,9 +31,6 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
 
     @abc.abstractmethod
     def prepare_distribution_metadata(
-        self,
-        finder: PackageFinder,
-        build_isolation: bool,
-        check_build_deps: bool,
+        self, finder: PackageFinder, build_isolation: bool
     ) -> None:
         raise NotImplementedError()
diff --git a/src/pip/_internal/distributions/installed.py b/src/pip/_internal/distributions/installed.py
index edb38aa1a..be5962f98 100644
--- a/src/pip/_internal/distributions/installed.py
+++ b/src/pip/_internal/distributions/installed.py
@@ -15,9 +15,6 @@ class InstalledDistribution(AbstractDistribution):
         return self.req.satisfied_by
 
     def prepare_distribution_metadata(
-        self,
-        finder: PackageFinder,
-        build_isolation: bool,
-        check_build_deps: bool,
+        self, finder: PackageFinder, build_isolation: bool
     ) -> None:
         pass
diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py
index 4c2564793..463c409d4 100644
--- a/src/pip/_internal/distributions/sdist.py
+++ b/src/pip/_internal/distributions/sdist.py
@@ -22,10 +22,7 @@ class SourceDistribution(AbstractDistribution):
         return self.req.get_dist()
 
     def prepare_distribution_metadata(
-        self,
-        finder: PackageFinder,
-        build_isolation: bool,
-        check_build_deps: bool,
+        self, finder: PackageFinder, build_isolation: bool
     ) -> None:
         # Load pyproject.toml, to determine whether PEP 517 is to be used
         self.req.load_pyproject_toml()
@@ -46,9 +43,7 @@ class SourceDistribution(AbstractDistribution):
             self.req.isolated_editable_sanity_check()
             # Install the dynamic build requirements.
             self._install_build_reqs(finder)
-        # Check if the current environment provides build dependencies
-        should_check_deps = self.req.use_pep517 and check_build_deps
-        if should_check_deps:
+        elif self.req.use_pep517:
             pyproject_requires = self.req.pyproject_requires
             assert pyproject_requires is not None
             conflicting, missing = self.req.build_env.check_requirements(
diff --git a/src/pip/_internal/distributions/wheel.py b/src/pip/_internal/distributions/wheel.py
index 03aac775b..340b0f3c5 100644
--- a/src/pip/_internal/distributions/wheel.py
+++ b/src/pip/_internal/distributions/wheel.py
@@ -26,9 +26,6 @@ class WheelDistribution(AbstractDistribution):
         return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
 
     def prepare_distribution_metadata(
-        self,
-        finder: PackageFinder,
-        build_isolation: bool,
-        check_build_deps: bool,
+        self, finder: PackageFinder, build_isolation: bool
     ) -> None:
         pass
diff --git a/src/pip/_internal/metadata/__init__.py b/src/pip/_internal/metadata/__init__.py
index 01c35f94b..aa9a80822 100644
--- a/src/pip/_internal/metadata/__init__.py
+++ b/src/pip/_internal/metadata/__init__.py
@@ -1,11 +1,7 @@
-import contextlib
 import functools
 import os
-import sys
 from typing import TYPE_CHECKING, List, Optional, Type, cast
 
-from pip._internal.utils.misc import strtobool
-
 from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
 
 if TYPE_CHECKING:
@@ -26,29 +22,6 @@ __all__ = [
 ]
 
 
-def _should_use_importlib_metadata() -> bool:
-    """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
-
-    By default, pip uses ``importlib.metadata`` on Python 3.11+, and
-    ``pkg_resourcess`` otherwise. This can be overriden by a couple of ways:
-
-    * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
-      dictates whether ``importlib.metadata`` is used, regardless of Python
-      version.
-    * On Python 3.11+, Python distributors can patch ``importlib.metadata``
-      to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This
-      makes pip use ``pkg_resources`` (unless the user set the aforementioned
-      environment variable to *True*).
-    """
-    with contextlib.suppress(KeyError, ValueError):
-        return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"]))
-    if sys.version_info < (3, 11):
-        return False
-    import importlib.metadata
-
-    return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True))
-
-
 class Backend(Protocol):
     Distribution: Type[BaseDistribution]
     Environment: Type[BaseEnvironment]
@@ -56,7 +29,7 @@ class Backend(Protocol):
 
 @functools.lru_cache(maxsize=None)
 def select_backend() -> Backend:
-    if _should_use_importlib_metadata():
+    if os.environ.get("_PIP_METADATA_BACKEND_IMPORTLIB"):
         from . import importlib
 
         return cast(Backend, importlib)
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index df1016ebb..7550d3a90 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -50,14 +50,11 @@ def _get_prepared_distribution(
     build_tracker: BuildTracker,
     finder: PackageFinder,
     build_isolation: bool,
-    check_build_deps: bool,
 ) -> BaseDistribution:
     """Prepare a distribution for installation."""
     abstract_dist = make_distribution_for_install_requirement(req)
     with build_tracker.track(req):
-        abstract_dist.prepare_distribution_metadata(
-            finder, build_isolation, check_build_deps
-        )
+        abstract_dist.prepare_distribution_metadata(finder, build_isolation)
     return abstract_dist.get_metadata_distribution()
 
 
@@ -202,7 +199,6 @@ class RequirementPreparer:
         download_dir: Optional[str],
         src_dir: str,
         build_isolation: bool,
-        check_build_deps: bool,
         build_tracker: BuildTracker,
         session: PipSession,
         progress_bar: str,
@@ -229,9 +225,6 @@ class RequirementPreparer:
         # Is build isolation allowed?
         self.build_isolation = build_isolation
 
-        # Should check build dependencies?
-        self.check_build_deps = check_build_deps
-
         # Should hash-checking be required?
         self.require_hashes = require_hashes
 
@@ -499,7 +492,6 @@ class RequirementPreparer:
             self.build_tracker,
             self.finder,
             self.build_isolation,
-            self.check_build_deps,
         )
         return dist
 
@@ -553,7 +545,6 @@ class RequirementPreparer:
                 self.build_tracker,
                 self.finder,
                 self.build_isolation,
-                self.check_build_deps,
             )
 
             req.check_if_exists(self.use_user_site)
diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py
index 0f550bf1e..6626c37e2 100644
--- a/src/pip/_internal/req/req_set.py
+++ b/src/pip/_internal/req/req_set.py
@@ -1,10 +1,13 @@
 import logging
 from collections import OrderedDict
-from typing import Dict, List
+from typing import Dict, Iterable, List, Optional, Tuple
 
 from pip._vendor.packaging.utils import canonicalize_name
 
+from pip._internal.exceptions import InstallationError
+from pip._internal.models.wheel import Wheel
 from pip._internal.req.req_install import InstallRequirement
+from pip._internal.utils import compatibility_tags
 
 logger = logging.getLogger(__name__)
 
@@ -48,6 +51,123 @@ class RequirementSet:
         project_name = canonicalize_name(install_req.name)
         self.requirements[project_name] = install_req
 
+    def add_requirement(
+        self,
+        install_req: InstallRequirement,
+        parent_req_name: Optional[str] = None,
+        extras_requested: Optional[Iterable[str]] = None,
+    ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
+        """Add install_req as a requirement to install.
+
+        :param parent_req_name: The name of the requirement that needed this
+            added. The name is used because when multiple unnamed requirements
+            resolve to the same name, we could otherwise end up with dependency
+            links that point outside the Requirements set. parent_req must
+            already be added. Note that None implies that this is a user
+            supplied requirement, vs an inferred one.
+        :param extras_requested: an iterable of extras used to evaluate the
+            environment markers.
+        :return: Additional requirements to scan. That is either [] if
+            the requirement is not applicable, or [install_req] if the
+            requirement is applicable and has just been added.
+        """
+        # If the markers do not match, ignore this requirement.
+        if not install_req.match_markers(extras_requested):
+            logger.info(
+                "Ignoring %s: markers '%s' don't match your environment",
+                install_req.name,
+                install_req.markers,
+            )
+            return [], None
+
+        # If the wheel is not supported, raise an error.
+        # Should check this after filtering out based on environment markers to
+        # allow specifying different wheels based on the environment/OS, in a
+        # single requirements file.
+        if install_req.link and install_req.link.is_wheel:
+            wheel = Wheel(install_req.link.filename)
+            tags = compatibility_tags.get_supported()
+            if self.check_supported_wheels and not wheel.supported(tags):
+                raise InstallationError(
+                    "{} is not a supported wheel on this platform.".format(
+                        wheel.filename
+                    )
+                )
+
+        # This next bit is really a sanity check.
+        assert (
+            not install_req.user_supplied or parent_req_name is None
+        ), "a user supplied req shouldn't have a parent"
+
+        # Unnamed requirements are scanned again and the requirement won't be
+        # added as a dependency until after scanning.
+        if not install_req.name:
+            self.add_unnamed_requirement(install_req)
+            return [install_req], None
+
+        try:
+            existing_req: Optional[InstallRequirement] = self.get_requirement(
+                install_req.name
+            )
+        except KeyError:
+            existing_req = None
+
+        has_conflicting_requirement = (
+            parent_req_name is None
+            and existing_req
+            and not existing_req.constraint
+            and existing_req.extras == install_req.extras
+            and existing_req.req
+            and install_req.req
+            and existing_req.req.specifier != install_req.req.specifier
+        )
+        if has_conflicting_requirement:
+            raise InstallationError(
+                "Double requirement given: {} (already in {}, name={!r})".format(
+                    install_req, existing_req, install_req.name
+                )
+            )
+
+        # When no existing requirement exists, add the requirement as a
+        # dependency and it will be scanned again after.
+        if not existing_req:
+            self.add_named_requirement(install_req)
+            # We'd want to rescan this requirement later
+            return [install_req], install_req
+
+        # Assume there's no need to scan, and that we've already
+        # encountered this for scanning.
+        if install_req.constraint or not existing_req.constraint:
+            return [], existing_req
+
+        does_not_satisfy_constraint = install_req.link and not (
+            existing_req.link and install_req.link.path == existing_req.link.path
+        )
+        if does_not_satisfy_constraint:
+            raise InstallationError(
+                "Could not satisfy constraints for '{}': "
+                "installation from path or url cannot be "
+                "constrained to a version".format(install_req.name)
+            )
+        # If we're now installing a constraint, mark the existing
+        # object for real installation.
+        existing_req.constraint = False
+        # If we're now installing a user supplied requirement,
+        # mark the existing object as such.
+        if install_req.user_supplied:
+            existing_req.user_supplied = True
+        existing_req.extras = tuple(
+            sorted(set(existing_req.extras) | set(install_req.extras))
+        )
+        logger.debug(
+            "Setting %s extras to: %s",
+            existing_req,
+            existing_req.extras,
+        )
+        # Return the existing requirement for addition to the parent and
+        # scanning again.
+        return [existing_req], existing_req
+
     def has_requirement(self, name: str) -> bool:
         project_name = canonicalize_name(name)
 
diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py
index 1225ae70f..8c149d437 100644
--- a/src/pip/_internal/resolution/legacy/resolver.py
+++ b/src/pip/_internal/resolution/legacy/resolver.py
@@ -28,14 +28,12 @@ from pip._internal.exceptions import (
     DistributionNotFound,
     HashError,
     HashErrors,
-    InstallationError,
     NoneMetadataError,
     UnsupportedPythonVersion,
 )
 from pip._internal.index.package_finder import PackageFinder
 from pip._internal.metadata import BaseDistribution
 from pip._internal.models.link import Link
-from pip._internal.models.wheel import Wheel
 from pip._internal.operations.prepare import RequirementPreparer
 from pip._internal.req.req_install import (
     InstallRequirement,
@@ -43,7 +41,6 @@ from pip._internal.req.req_install import (
 )
 from pip._internal.req.req_set import RequirementSet
 from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
-from pip._internal.utils import compatibility_tags
 from pip._internal.utils.compatibility_tags import get_supported
 from pip._internal.utils.logging import indent_log
 from pip._internal.utils.misc import normalize_version_info
@@ -171,7 +168,7 @@ class Resolver(BaseResolver):
         for req in root_reqs:
             if req.constraint:
                 check_invalid_constraint_type(req)
-            self._add_requirement_to_set(requirement_set, req)
+            requirement_set.add_requirement(req)
 
         # Actually prepare the files, and collect any exceptions. Most hash
         # exceptions cannot be checked ahead of time, because
@@ -191,124 +188,6 @@ class Resolver(BaseResolver):
 
         return requirement_set
 
-    def _add_requirement_to_set(
-        self,
-        requirement_set: RequirementSet,
-        install_req: InstallRequirement,
-        parent_req_name: Optional[str] = None,
-        extras_requested: Optional[Iterable[str]] = None,
-    ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
-        """Add install_req as a requirement to install.
-
-        :param parent_req_name: The name of the requirement that needed this
-            added. The name is used because when multiple unnamed requirements
-            resolve to the same name, we could otherwise end up with dependency
-            links that point outside the Requirements set. parent_req must
-            already be added. Note that None implies that this is a user
-            supplied requirement, vs an inferred one.
-        :param extras_requested: an iterable of extras used to evaluate the
-            environment markers.
-        :return: Additional requirements to scan. That is either [] if
-            the requirement is not applicable, or [install_req] if the
-            requirement is applicable and has just been added.
-        """
-        # If the markers do not match, ignore this requirement.
-        if not install_req.match_markers(extras_requested):
-            logger.info(
-                "Ignoring %s: markers '%s' don't match your environment",
-                install_req.name,
-                install_req.markers,
-            )
-            return [], None
-
-        # If the wheel is not supported, raise an error.
-        # Should check this after filtering out based on environment markers to
-        # allow specifying different wheels based on the environment/OS, in a
-        # single requirements file.
-        if install_req.link and install_req.link.is_wheel:
-            wheel = Wheel(install_req.link.filename)
-            tags = compatibility_tags.get_supported()
-            if requirement_set.check_supported_wheels and not wheel.supported(tags):
-                raise InstallationError(
-                    "{} is not a supported wheel on this platform.".format(
-                        wheel.filename
-                    )
-                )
-
-        # This next bit is really a sanity check.
-        assert (
-            not install_req.user_supplied or parent_req_name is None
-        ), "a user supplied req shouldn't have a parent"
-
-        # Unnamed requirements are scanned again and the requirement won't be
-        # added as a dependency until after scanning.
-        if not install_req.name:
-            requirement_set.add_unnamed_requirement(install_req)
-            return [install_req], None
-
-        try:
-            existing_req: Optional[
-                InstallRequirement
-            ] = requirement_set.get_requirement(install_req.name)
-        except KeyError:
-            existing_req = None
-
-        has_conflicting_requirement = (
-            parent_req_name is None
-            and existing_req
-            and not existing_req.constraint
-            and existing_req.extras == install_req.extras
-            and existing_req.req
-            and install_req.req
-            and existing_req.req.specifier != install_req.req.specifier
-        )
-        if has_conflicting_requirement:
-            raise InstallationError(
-                "Double requirement given: {} (already in {}, name={!r})".format(
-                    install_req, existing_req, install_req.name
-                )
-            )
-
-        # When no existing requirement exists, add the requirement as a
-        # dependency and it will be scanned again after.
-        if not existing_req:
-            requirement_set.add_named_requirement(install_req)
-            # We'd want to rescan this requirement later
-            return [install_req], install_req
-
-        # Assume there's no need to scan, and that we've already
-        # encountered this for scanning.
-        if install_req.constraint or not existing_req.constraint:
-            return [], existing_req
-
-        does_not_satisfy_constraint = install_req.link and not (
-            existing_req.link and install_req.link.path == existing_req.link.path
-        )
-        if does_not_satisfy_constraint:
-            raise InstallationError(
-                "Could not satisfy constraints for '{}': "
-                "installation from path or url cannot be "
-                "constrained to a version".format(install_req.name)
-            )
-        # If we're now installing a constraint, mark the existing
-        # object for real installation.
-        existing_req.constraint = False
-        # If we're now installing a user supplied requirement,
-        # mark the existing object as such.
-        if install_req.user_supplied:
-            existing_req.user_supplied = True
-        existing_req.extras = tuple(
-            sorted(set(existing_req.extras) | set(install_req.extras))
-        )
-        logger.debug(
-            "Setting %s extras to: %s",
-            existing_req,
-            existing_req.extras,
-        )
-        # Return the existing requirement for addition to the parent and
-        # scanning again.
-        return [existing_req], existing_req
-
     def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
         if self.upgrade_strategy == "to-satisfy-only":
             return False
@@ -514,8 +393,7 @@ class Resolver(BaseResolver):
             # the legacy resolver so I'm just not going to bother refactoring.
             sub_install_req = self._make_install_req(str(subreq), req_to_install)
             parent_req_name = req_to_install.name
-            to_scan_again, add_to_parent = self._add_requirement_to_set(
-                requirement_set,
+            to_scan_again, add_to_parent = requirement_set.add_requirement(
                 sub_install_req,
                 parent_req_name=parent_req_name,
                 extras_requested=extras_requested,
@@ -532,9 +410,7 @@ class Resolver(BaseResolver):
                 # 'unnamed' requirements can only come from being directly
                 # provided by the user.
                 assert req_to_install.user_supplied
-                self._add_requirement_to_set(
-                    requirement_set, req_to_install, parent_req_name=None
-                )
+                requirement_set.add_requirement(req_to_install, parent_req_name=None)
 
             if not self.ignore_dependencies:
                 if req_to_install.extras:


Test patch:
diff --git a/tests/functional/test_build_env.py b/tests/functional/test_build_env.py
index 1b9f42d94..a7f89687c 100644
--- a/tests/functional/test_build_env.py
+++ b/tests/functional/test_build_env.py
@@ -178,7 +178,6 @@ def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
             [
                 "bar==2.0; python_version < '3.0'",
                 "bar==3.0; python_version >= '3.0'",
-                "foo==4.0; extra == 'dev'",
             ],
         )
         assert r == (set(), set()), repr(r)
diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py
index 335cd8408..bf5f58fbb 100644
--- a/tests/functional/test_pep517.py
+++ b/tests/functional/test_pep517.py
@@ -5,7 +5,13 @@ import tomli_w
 
 from pip._internal.build_env import BuildEnvironment
 from pip._internal.req import InstallRequirement
-from tests.lib import PipTestEnvironment, TestData, make_test_finder, path_to_url
+from tests.lib import (
+    PipTestEnvironment,
+    TestData,
+    create_basic_wheel_for_package,
+    make_test_finder,
+    path_to_url,
+)
 from tests.lib.path import Path
 
 
@@ -160,26 +166,6 @@ def test_conflicting_pep517_backend_requirements(
     assert result.returncode != 0 and msg in result.stderr, str(result)
 
 
-def test_no_check_build_deps(
-    script: PipTestEnvironment, tmpdir: Path, data: TestData
-) -> None:
-    project_dir = make_project(
-        tmpdir, requires=["simplewheel==2.0"], backend="test_backend"
-    )
-    script.pip(
-        "install",
-        "simplewheel==1.0",
-        "test_backend",
-        "--no-index",
-        "-f",
-        data.packages,
-        "-f",
-        data.backends,
-    )
-    result = script.pip("install", "--no-build-isolation", project_dir)
-    result.assert_installed("project", editable=False)
-
-
 def test_validate_missing_pep517_backend_requirements(
     script: PipTestEnvironment, tmpdir: Path, data: TestData
 ) -> None:
@@ -194,7 +180,6 @@ def test_validate_missing_pep517_backend_requirements(
         "-f",
         data.packages,
         "--no-build-isolation",
-        "--check-build-dependencies",
         project_dir,
         expect_error=True,
     )
@@ -220,7 +205,6 @@ def test_validate_conflicting_pep517_backend_requirements(
         "-f",
         data.packages,
         "--no-build-isolation",
-        "--check-build-dependencies",
         project_dir,
         expect_error=True,
     )
@@ -232,6 +216,25 @@ def test_validate_conflicting_pep517_backend_requirements(
     assert result.returncode != 0 and msg in result.stderr, str(result)
 
 
+def test_pep517_backend_requirements_satisfied_by_prerelease(
+    script: PipTestEnvironment,
+    data: TestData,
+) -> None:
+    create_basic_wheel_for_package(script, "myreq", "1.0a1")
+    script.pip("install", "myreq==1.0a1", "--no-index", "-f", script.scratch_path)
+    script.pip("install", "test_backend", "--no-index", "-f", data.backends)
+
+    project_dir = make_project(
+        script.temp_path,
+        requires=["test_backend", "myreq"],
+        backend="test_backend",
+    )
+    project_dir.joinpath("backend_reqs.txt").write_text("myreq")
+
+    result = script.pip("install", "--no-index", "--no-build-isolation", project_dir)
+    assert "Installing backend dependencies:" not in result.stdout
+
+
 def test_pep517_backend_requirements_already_satisfied(
     script: PipTestEnvironment, tmpdir: Path, data: TestData
 ) -> None:
diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py
index ac68fa4df..74f4a8bab 100644
--- a/tests/unit/test_req.py
+++ b/tests/unit/test_req.py
@@ -91,7 +91,6 @@ class TestRequirementSet:
                 src_dir=os.path.join(self.tempdir, "src"),
                 download_dir=None,
                 build_isolation=True,
-                check_build_deps=False,
                 build_tracker=tracker,
                 session=session,
                 progress_bar="on",
@@ -124,7 +123,7 @@ class TestRequirementSet:
         reqset = RequirementSet()
         req = install_req_from_line("simple")
         req.user_supplied = True
-        reqset.add_named_requirement(req)
+        reqset.add_requirement(req)
         finder = make_test_finder(find_links=[data.find_links])
         with self._basic_resolver(finder) as resolver:
             with pytest.raises(
@@ -145,7 +144,7 @@ class TestRequirementSet:
         reqset = RequirementSet()
         req = install_req_from_editable(data.packages.joinpath("LocalEnvironMarker"))
         req.user_supplied = True
-        reqset.add_unnamed_requirement(req)
+        reqset.add_requirement(req)
         finder = make_test_finder(find_links=[data.find_links])
         with self._basic_resolver(finder) as resolver:
             reqset = resolver.resolve(reqset.all_requirements, True)
@@ -156,9 +155,7 @@ class TestRequirementSet:
         are missing.
         """
         reqset = RequirementSet()
-        reqset.add_named_requirement(
-            get_processed_req_from_line("simple==1.0", lineno=1)
-        )
+        reqset.add_requirement(get_processed_req_from_line("simple==1.0", lineno=1))
 
         finder = make_test_finder(find_links=[data.find_links])
 
@@ -197,14 +194,14 @@ class TestRequirementSet:
 
         """
         reqset = RequirementSet()
-        reqset.add_unnamed_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 "git+git://github.com/pypa/pip-test-package --hash=sha256:123",
                 lineno=1,
             )
         )
         dir_path = data.packages.joinpath("FSPkg")
-        reqset.add_unnamed_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 f"file://{dir_path}",
                 lineno=2,
@@ -238,7 +235,7 @@ class TestRequirementSet:
         """
         reqset = RequirementSet()
         # Test that there must be exactly 1 specifier:
-        reqset.add_named_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 "simple --hash=sha256:a90427ae31f5d1d0d7ec06ee97d9fcf2d0fc9a786985"
                 "250c1c83fd68df5911dd",
@@ -246,7 +243,7 @@ class TestRequirementSet:
             )
         )
         # Test that the operator must be ==:
-        reqset.add_named_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 "simple2>1.0 --hash=sha256:3ad45e1e9aa48b4462af0"
                 "123f6a7e44a9115db1ef945d4d92c123dfe21815a06",
@@ -270,7 +267,7 @@ class TestRequirementSet:
         """A hash mismatch should raise an error."""
         file_url = path_to_url((data.packages / "simple-1.0.tar.gz").resolve())
         reqset = RequirementSet()
-        reqset.add_unnamed_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 f"{file_url} --hash=sha256:badbad",
                 lineno=1,
@@ -295,7 +292,7 @@ class TestRequirementSet:
         dependencies get complained about when --require-hashes is on."""
         reqset = RequirementSet()
         finder = make_test_finder(find_links=[data.find_links])
-        reqset.add_named_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 "TopoRequires2==0.0.1 "  # requires TopoRequires
                 "--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd"
@@ -325,7 +322,7 @@ class TestRequirementSet:
 
         """
         reqset = RequirementSet()
-        reqset.add_named_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 "TopoRequires2==0.0.1 "  # requires TopoRequires
                 "--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd"
@@ -333,7 +330,7 @@ class TestRequirementSet:
                 lineno=1,
             )
         )
-        reqset.add_named_requirement(
+        reqset.add_requirement(
             get_processed_req_from_line(
                 "TopoRequires==0.0.1 "
                 "--hash=sha256:d6dd1e22e60df512fdcf3640ced3039b3b02a56ab2cee81ebcb"
@@ -385,6 +382,32 @@ class TestInstallRequirement:
         assert req.link.scheme == "https"
         assert req.link.url == url
 
+    def test_unsupported_wheel_link_requirement_raises(self) -> None:
+        reqset = RequirementSet()
+        req = install_req_from_line(
+            "https://whatever.com/peppercorn-0.4-py2.py3-bogus-any.whl",
+        )
+        assert req.link is not None
+        assert req.link.is_wheel
+        assert req.link.scheme == "https"
+
+        with pytest.raises(InstallationError):
+            reqset.add_requirement(req)
+
+    def test_unsupported_wheel_local_file_requirement_raises(
+        self, data: TestData
+    ) -> None:
+        reqset = RequirementSet()
+        req = install_req_from_line(
+            data.packages.joinpath("simple.dist-0.1-py1-none-invalid.whl"),
+        )
+        assert req.link is not None
+        assert req.link.is_wheel
+        assert req.link.scheme == "file"
+
+        with pytest.raises(InstallationError):
+            reqset.add_requirement(req)
+
     def test_str(self) -> None:
         req = install_req_from_line("simple==0.1")
         assert str(req) == "simple==0.1"
@@ -635,6 +658,19 @@ def test_parse_editable_local_extras(
     )
 
 
+def test_exclusive_environment_markers() -> None:
+    """Make sure RequirementSet accepts several excluding env markers"""
+    eq36 = install_req_from_line("Django>=1.6.10,<1.7 ; python_version == '3.6'")
+    eq36.user_supplied = True
+    ne36 = install_req_from_line("Django>=1.6.10,<1.8 ; python_version != '3.6'")
+    ne36.user_supplied = True
+
+    req_set = RequirementSet()
+    req_set.add_requirement(eq36)
+    req_set.add_requirement(ne36)
+    assert req_set.has_requirement("Django")
+
+
 def test_mismatched_versions(caplog: pytest.LogCaptureFixture) -> None:
     req = InstallRequirement(
         req=Requirement("simplewheel==2.0"),
diff --git a/tests/unit/test_resolution_legacy_resolver.py b/tests/unit/test_resolution_legacy_resolver.py
index 134ff65b4..7b5ba6372 100644
--- a/tests/unit/test_resolution_legacy_resolver.py
+++ b/tests/unit/test_resolution_legacy_resolver.py
@@ -7,20 +7,15 @@ import pytest
 from pip._vendor.packaging.specifiers import SpecifierSet
 from pip._vendor.packaging.utils import NormalizedName
 
-from pip._internal.exceptions import (
-    InstallationError,
-    NoneMetadataError,
-    UnsupportedPythonVersion,
-)
+from pip._internal.exceptions import NoneMetadataError, UnsupportedPythonVersion
 from pip._internal.metadata import BaseDistribution
 from pip._internal.models.candidate import InstallationCandidate
 from pip._internal.req.constructors import install_req_from_line
-from pip._internal.req.req_set import RequirementSet
 from pip._internal.resolution.legacy.resolver import (
     Resolver,
     _check_dist_requires_python,
 )
-from tests.lib import TestData, make_test_finder
+from tests.lib import make_test_finder
 from tests.lib.index import make_mock_candidate
 
 T = TypeVar("T")
@@ -55,94 +50,8 @@ def make_fake_dist(
     return klass(metadata)  # type: ignore[call-arg]
 
 
-def make_test_resolver(
-    monkeypatch: pytest.MonkeyPatch,
-    mock_candidates: List[InstallationCandidate],
-) -> Resolver:
-    def _find_candidates(project_name: str) -> List[InstallationCandidate]:
-        return mock_candidates
-
-    finder = make_test_finder()
-    monkeypatch.setattr(finder, "find_all_candidates", _find_candidates)
-
-    return Resolver(
-        finder=finder,
-        preparer=mock.Mock(),  # Not used.
-        make_install_req=install_req_from_line,
-        wheel_cache=None,
-        use_user_site=False,
-        force_reinstall=False,
-        ignore_dependencies=False,
-        ignore_installed=False,
-        ignore_requires_python=False,
-        upgrade_strategy="to-satisfy-only",
-    )
-
-
-class TestAddRequirement:
-    """
-    Test _add_requirement_to_set().
-    """
-
-    def test_unsupported_wheel_link_requirement_raises(
-        self, monkeypatch: pytest.MonkeyPatch
-    ) -> None:
-        # GIVEN
-        resolver = make_test_resolver(monkeypatch, [])
-        requirement_set = RequirementSet(check_supported_wheels=True)
-
-        install_req = install_req_from_line(
-            "https://whatever.com/peppercorn-0.4-py2.py3-bogus-any.whl",
-        )
-        assert install_req.link is not None
-        assert install_req.link.is_wheel
-        assert install_req.link.scheme == "https"
-
-        # WHEN / THEN
-        with pytest.raises(InstallationError):
-            resolver._add_requirement_to_set(requirement_set, install_req)
-
-    def test_unsupported_wheel_local_file_requirement_raises(
-        self, data: TestData, monkeypatch: pytest.MonkeyPatch
-    ) -> None:
-        # GIVEN
-        resolver = make_test_resolver(monkeypatch, [])
-        requirement_set = RequirementSet(check_supported_wheels=True)
-
-        install_req = install_req_from_line(
-            data.packages.joinpath("simple.dist-0.1-py1-none-invalid.whl"),
-        )
-        assert install_req.link is not None
-        assert install_req.link.is_wheel
-        assert install_req.link.scheme == "file"
-
-        # WHEN / THEN
-        with pytest.raises(InstallationError):
-            resolver._add_requirement_to_set(requirement_set, install_req)
-
-    def test_exclusive_environment_markers(
-        self, monkeypatch: pytest.MonkeyPatch
-    ) -> None:
-        """Make sure excluding environment markers are handled correctly."""
-        # GIVEN
-        resolver = make_test_resolver(monkeypatch, [])
-        requirement_set = RequirementSet(check_supported_wheels=True)
-
-        eq36 = install_req_from_line("Django>=1.6.10,<1.7 ; python_version == '3.6'")
-        eq36.user_supplied = True
-        ne36 = install_req_from_line("Django>=1.6.10,<1.8 ; python_version != '3.6'")
-        ne36.user_supplied = True
-
-        # WHEN
-        resolver._add_requirement_to_set(requirement_set, eq36)
-        resolver._add_requirement_to_set(requirement_set, ne36)
-
-        # THEN
-        assert requirement_set.has_requirement("Django")
-        assert len(requirement_set.all_requirements) == 1
-
-
 class TestCheckDistRequiresPython:
+
     """
     Test _check_dist_requires_python().
     """
@@ -270,6 +179,30 @@ class TestYankedWarning:
     Test _populate_link() emits warning if one or more candidates are yanked.
     """
 
+    def _make_test_resolver(
+        self,
+        monkeypatch: pytest.MonkeyPatch,
+        mock_candidates: List[InstallationCandidate],
+    ) -> Resolver:
+        def _find_candidates(project_name: str) -> List[InstallationCandidate]:
+            return mock_candidates
+
+        finder = make_test_finder()
+        monkeypatch.setattr(finder, "find_all_candidates", _find_candidates)
+
+        return Resolver(
+            finder=finder,
+            preparer=mock.Mock(),  # Not used.
+            make_install_req=install_req_from_line,
+            wheel_cache=None,
+            use_user_site=False,
+            force_reinstall=False,
+            ignore_dependencies=False,
+            ignore_installed=False,
+            ignore_requires_python=False,
+            upgrade_strategy="to-satisfy-only",
+        )
+
     def test_sort_best_candidate__has_non_yanked(
         self, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
     ) -> None:
@@ -286,7 +219,7 @@ class TestYankedWarning:
         ]
         ireq = install_req_from_line("pkg")
 
-        resolver = make_test_resolver(monkeypatch, candidates)
+        resolver = self._make_test_resolver(monkeypatch, candidates)
         resolver._populate_link(ireq)
 
         assert ireq.link == candidates[0].link
@@ -310,7 +243,7 @@ class TestYankedWarning:
         ]
         ireq = install_req_from_line("pkg")
 
-        resolver = make_test_resolver(monkeypatch, candidates)
+        resolver = self._make_test_resolver(monkeypatch, candidates)
         resolver._populate_link(ireq)
 
         assert ireq.link == candidates[1].link
@@ -354,7 +287,7 @@ class TestYankedWarning:
         ]
         ireq = install_req_from_line("pkg")
 
-        resolver = make_test_resolver(monkeypatch, candidates)
+        resolver = self._make_test_resolver(monkeypatch, candidates)
         resolver._populate_link(ireq)
 
         assert ireq.link == candidates[0].link