Repository: pypa/pip
Pull request #13452 (2025-06-28 01:17:48)

refactor: Add BuildEnvironmentInstaller protocol
Towards #13450. This enables alternative build dependency installers to be added. This is a step towards installing build dependencies in-process.



Patch:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index e45b66dc9..35bbede70 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,8 @@
 # Documentation for this file can be found at:
 # https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository
 
-blank_issues_enabled: false
-contact_links:
-  - name: "(maintainers only) Blank issue"
-    url: https://github.com/pypa/pip/issues/new
-    about: For maintainers only.
+blank_issues_enabled: true
+# contact_links:
+#   - name: "(maintainers only) Blank issue"
+#     url: https://github.com/pypa/pip/issues/new
+#     about: For maintainers only.
diff --git a/.gitignore b/.gitignore
index 79b8ab84b..d2d5eadfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
-# Byte-compiled / optimized / DLL files
+# Byte-compiled files
 __pycache__/
-*.py[cod]
 *$py.class
 
 # Distribution / packaging
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ec9510f23..c43dc5ef1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,13 +22,13 @@ repos:
   - id: black
 
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.11.12
+  rev: v0.12.2
   hooks:
     - id: ruff-check
       args: [--fix]
 
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v1.16.0
+  rev: v1.16.1
   hooks:
   - id: mypy
     exclude: tests/data
diff --git a/MANIFEST.in b/MANIFEST.in
index 95e966d96..998cb4f48 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,3 @@
-include AUTHORS.txt
-include LICENSE.txt
 include NEWS.rst
 include README.rst
 include SECURITY.md
@@ -12,8 +10,6 @@ include build-project/.python-version
 
 include src/pip/_vendor/README.rst
 include src/pip/_vendor/vendor.txt
-recursive-include  src/pip/_vendor *LICENSE*
-recursive-include  src/pip/_vendor *COPYING*
 
 include docs/requirements.txt
 
diff --git a/docs/html/development/contributing.rst b/docs/html/development/contributing.rst
index b2f6f1d13..cf8cceb33 100644
--- a/docs/html/development/contributing.rst
+++ b/docs/html/development/contributing.rst
@@ -35,6 +35,15 @@ Examples include re-flowing text in comments or documentation, or adding or
 removing blank lines or whitespace within lines. Such changes can be made
 separately, as a "formatting cleanup" PR, if needed.
 
+.. attention::
+
+   While contributors may use whatever tools they like when developing a pull
+   request, it is the contributor's responsibility to ensure that submitted
+   code meets the project requirements, and that they understand the submitted
+   code well enough to respond to review comments.
+
+   In particular, we will mark LLM-generated slop as spam without additional
+   discussion.
 
 Automated Testing
 =================
diff --git a/docs/html/ux-research-design/guidance.md b/docs/html/ux-research-design/guidance.md
index 035df4c73..d5f058a2b 100644
--- a/docs/html/ux-research-design/guidance.md
+++ b/docs/html/ux-research-design/guidance.md
@@ -344,6 +344,24 @@ Designing for pip includes:
 - Writing pip's _output_ - establishing how pip responds to commands and what information it provides the user. This includes writing success and error messages.
 - Providing supplemental materials - e.g. documentation that helps users understand pip's operation
 
+### Error Message Format
+
+A good error message should mention:
+
+* what the user has tried to do
+* possible next steps to try and solve the error
+  * possible steps need to go from "easiest" to "most complicated"
+* why the error has happened - include a way to see more information
+  about the situation
+
+A [sample `ResolutionImpossible` error that follows this guidance
+is available](resolution-impossible-example).
+
+**Further reading**
+
+- <https://uxplanet.org/how-to-write-good-error-messages-858e4551cd4>
+- <https://www.nngroup.com/articles/error-message-guidelines/>
+
 ### Design Principles / Usability Heuristics
 
 There are many interaction design principles that help designers design great experiences. Nielsen Norman's [10 Usability Heuristics for User Interface Design](https://www.nngroup.com/articles/ten-usability-heuristics) is a great place to start. Here are some of the ways these principles apply to pip:
diff --git a/docs/html/ux-research-design/resolution-impossible-example.md b/docs/html/ux-research-design/resolution-impossible-example.md
new file mode 100644
index 000000000..a22bb93ec
--- /dev/null
+++ b/docs/html/ux-research-design/resolution-impossible-example.md
@@ -0,0 +1,92 @@
+---
+orphan:
+---
+
+# Example error: ResolutionImpossible
+
+## What if there are user-provided pinned packages?
+
+Where a user wants to install packages (with 1 or more pinned version)
+with default verbose level
+
+```
+$ pip install peach=1.0 apple=2.0
+
+Due to conflicting dependencies pip cannot install Peach1.0 and Apple2.0:
+
+* Peach 1.0 depends on Banana 3.0
+* Apple2.0 depends on Banana2.0
+
+There are a number of possible solutions. You can try:
+1. removing package versions from your requirements, and letting pip try to resolve the problem for you
+2. Trying  a version of Peach that depends on Banana2.0. Try `pip-search peach —dep banana2.0`
+3. replacing Apple or Peach with a different package altogether
+4. patching Apple2.0 to use Banana3.0
+5. force installing (Be aware!)
+
+For instructions on how to do these steps visit: https://pypa.io/SomeLink
+To debug this further you can run `pip-tree` to see all of your dependencies.
+```
+
+**with verbose level -vv**
+
+If a user ran the same pip command with more verbosity, what would they see?
+
+**with verbose level -vvv**
+
+If a user ran the same pip command with more verbosity, what would they see?
+
+## What if there are no user-provided version restrictions?
+
+NB: We are assuming this resolver behaviour gets implemented, based on [GH issues 8249](https://github.com/pypa/pip/issues/8249).
+
+**with default verbose level**
+
+```
+$ pip install apple peach
+
+Due to conflicting dependencies pip cannot install apple or peach. Both depend on banana, but pip can't find a version of either where they depend on the same banana version.
+
+There are a number of possible solutions. You can try:
+1. replacing apple or peach with a different package altogether
+2. patching apple or peach to use the same version of banana
+3. force installing (Be aware!)
+
+To debug this further you can run pip-tree to see all of your dependencies.
+```
+
+**with verbose level -vv**
+
+If a user ran the same pip command with more verbosity, what would they see?
+
+**with verbose level -vvv**
+
+If a user ran the same pip command with more verbosity, what would they see?
+
+**What should be in the "documentation" page?**
+
+* ways to swap a package for another
+* how to patch a package to support a version (various ways)
+
+## Recommendations
+
+* Write official documentation / guide "How to resolve dependency conflicts" explaining:
+  * Why conflicts can exist
+  * How you can avoid them (pinning)
+  * How you can resolve them
+    * Use alternative package
+    * Use older version
+    * Patch package
+* Introduce new commands to pip, inspired by poetry:
+  * Tree: Show full tree of dependencies
+  * Show `<package>` Show details of a package, including it's dependencies
+  * latest - shows latest vs installed versions
+  * outdated - shows only outdated versions
+* Expose commands / help link in output??
+  * when particular issue happens provide ways to move on (ala pipenv), e.g.
+    * run this command to see X
+    * is it your internet connection?
+    * is it the pypi website?
+* Aspirational commands
+  * `pip search PackageName —dep PackageNameVersion`
+    * a command that will search for a version of a package that has a dependency on another packageversion
diff --git a/news/10915.bugfix.rst b/news/10915.bugfix.rst
new file mode 100644
index 000000000..0f4bb5809
--- /dev/null
+++ b/news/10915.bugfix.rst
@@ -0,0 +1,5 @@
+Suppress the progress bar, when running with ``--log`` and ``--quiet``.
+
+Consequently, a new ``auto`` mode for ``--progress-bar`` has been added.
+``auto`` will enable progress bars unless suppressed by ``--quiet``,
+while ``on`` will always enable progress bars.
diff --git a/news/12099.bugfix.rst b/news/12099.bugfix.rst
new file mode 100644
index 000000000..b174df532
--- /dev/null
+++ b/news/12099.bugfix.rst
@@ -0,0 +1,2 @@
+``pip config debug`` now correctly separates options as set by the different files
+at the same level.
diff --git a/news/12283.feature.rst b/news/12283.feature.rst
new file mode 100644
index 000000000..1e2f2237c
--- /dev/null
+++ b/news/12283.feature.rst
@@ -0,0 +1,1 @@
+Remove warning when cloning from a Git reference that does not look like a commit hash.
diff --git a/news/13335.process.rst b/news/13335.process.rst
new file mode 100644
index 000000000..9a38395bb
--- /dev/null
+++ b/news/13335.process.rst
@@ -0,0 +1,3 @@
+pip's own licensing metadata now follows PEP 639.
+In addition, the licenses of pip's vendored dependencies are now included
+in the ``License-File`` metadata field and in the wheel.
diff --git a/news/13424.feature.rst b/news/13424.feature.rst
new file mode 100644
index 000000000..2829abeb9
--- /dev/null
+++ b/news/13424.feature.rst
@@ -0,0 +1,1 @@
+Minor performance improvement getting the order to install a very large number of interdependent packages.
diff --git a/news/13464.feature.rst b/news/13464.feature.rst
new file mode 100644
index 000000000..518b89e42
--- /dev/null
+++ b/news/13464.feature.rst
@@ -0,0 +1,1 @@
+Automatic download resumption and retrying is enabled by default.
diff --git a/news/627ddbb6-5268-4fd9-b04b-0485abc35295.trivial.rst b/news/627ddbb6-5268-4fd9-b04b-0485abc35295.trivial.rst
new file mode 100644
index 000000000..e69de29bb
diff --git a/noxfile.py b/noxfile.py
index 14097bf87..160e823e9 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -26,8 +26,6 @@ LOCATIONS = {
 }
 REQUIREMENTS = {
     "docs": "docs/requirements.txt",
-    "tests": "tests/requirements.txt",
-    "common-wheels": "tests/requirements-common_wheels.txt",
 }
 
 AUTHORS_FILE = "AUTHORS.txt"
@@ -54,10 +52,10 @@ def should_update_common_wheels() -> bool:
     if not os.path.exists(LOCATIONS["common-wheels"]):
         return True
 
-    # If the requirements was updated after cache, we'll repopulate it.
+    # If the pyproject.toml was updated after cache, we'll repopulate it.
     cache_last_populated_at = os.path.getmtime(LOCATIONS["common-wheels"])
-    requirements_updated_at = os.path.getmtime(REQUIREMENTS["common-wheels"])
-    need_to_repopulate = requirements_updated_at > cache_last_populated_at
+    pyproject_updated_at = os.path.getmtime("pyproject.toml")
+    need_to_repopulate = pyproject_updated_at > cache_last_populated_at
 
     # Clear the stale cache.
     if need_to_repopulate:
@@ -78,7 +76,7 @@ def test(session: nox.Session) -> None:
             session,
             "wheel",
             "-w", LOCATIONS["common-wheels"],
-            "-r", REQUIREMENTS["common-wheels"],
+            "--group", "test-common-wheels",
         )
         # fmt: on
     else:
@@ -115,7 +113,7 @@ def test(session: nox.Session) -> None:
     run_with_protected_pip(session, "install", generated_sdist)
 
     # Install test dependencies
-    run_with_protected_pip(session, "install", "-r", REQUIREMENTS["tests"])
+    run_with_protected_pip(session, "install", "--group", "test")
 
     # Parallelize tests as much as possible, by default.
     arguments = session.posargs or ["-n", "auto"]
@@ -156,6 +154,7 @@ def docs(session: nox.Session) -> None:
         ]
         # fmt: on
 
+    shutil.rmtree("docs/build", ignore_errors=True)
     session.run(*get_sphinx_build_command("html"))
     session.run(*get_sphinx_build_command("man"))
 
diff --git a/pyproject.toml b/pyproject.toml
index 1f74588d6..90f04307f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,11 +4,16 @@ dynamic = ["version"]
 name = "pip"
 description = "The PyPA recommended tool for installing Python packages."
 readme = "README.rst"
-license = {text = "MIT"}
+license = "MIT"
+license-files = [
+  "AUTHORS.txt",
+  "LICENSE.txt",
+  "src/pip/_vendor/**/*COPYING*",
+  "src/pip/_vendor/**/*LICENSE*",
+]
 classifiers = [
   "Development Status :: 5 - Production/Stable",
   "Intended Audience :: Developers",
-  "License :: OSI Approved :: MIT License",
   "Topic :: Software Development :: Build Tools",
   "Programming Language :: Python",
   "Programming Language :: Python :: 3",
@@ -40,10 +45,39 @@ Source = "https://github.com/pypa/pip"
 Changelog = "https://pip.pypa.io/en/stable/news/"
 
 [build-system]
-# The lower bound is for <https://github.com/pypa/setuptools/issues/3865>.
-requires = ["setuptools>=67.6.1"]
+requires = ["setuptools>=77"]
 build-backend = "setuptools.build_meta"
 
+[dependency-groups]
+test = [
+    "cryptography",
+    "freezegun",
+    "installer",
+    "pytest",
+    "pytest-cov",
+    "pytest-rerunfailures",
+    "pytest-xdist",
+    "scripttest",
+    "setuptools",
+    # macOS (darwin) arm64 always uses virtualenv >= 20.0
+    # for other platforms, it depends on python version
+    "virtualenv < 20.0 ; python_version < '3.10' and (sys_platform != 'darwin' or platform_machine != 'arm64')",
+    "virtualenv >= 20.0 ; python_version >= '3.10' or (sys_platform == 'darwin' and platform_machine == 'arm64')",
+    "werkzeug",
+    "wheel",
+    "tomli-w",
+    "proxy.py",
+]
+
+test-common-wheels = [
+    # We pin setuptools<80 because our test suite currently
+    # depends on setup.py develop to generate egg-link files.
+    "setuptools >= 40.8.0, != 60.6.0, <80",
+    "wheel",
+    # As required by pytest-cov.
+    "coverage >= 4.4",
+]
+
 [tool.setuptools]
 package-dir = {"" = "src"}
 include-package-data = false
diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py
index 69a2e72ce..3a246a1e3 100644
--- a/src/pip/_internal/build_env.py
+++ b/src/pip/_internal/build_env.py
@@ -11,7 +11,7 @@ import textwrap
 from collections import OrderedDict
 from collections.abc import Iterable
 from types import TracebackType
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Protocol
 
 from pip._vendor.packaging.version import Version
 
@@ -26,6 +26,7 @@ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
 
 if TYPE_CHECKING:
     from pip._internal.index.package_finder import PackageFinder
+    from pip._internal.req.req_install import InstallRequirement
 
 logger = logging.getLogger(__name__)
 
@@ -79,10 +80,108 @@ def _get_system_sitepackages() -> set[str]:
     return {os.path.normcase(path) for path in system_sites}
 
 
+class BuildEnvironmentInstaller(Protocol):
+    """
+    Interface for installing build dependencies into an isolated build
+    environment.
+    """
+
+    def install(
+        self,
+        requirements: Iterable[str],
+        prefix: _Prefix,
+        *,
+        kind: str,
+        for_req: InstallRequirement | None,
+    ) -> None: ...
+
+
+class SubprocessBuildEnvironmentInstaller:
+    """
+    Install build dependencies by calling pip in a subprocess.
+    """
+
+    def __init__(self, finder: PackageFinder) -> None:
+        self.finder = finder
+
+    def install(
+        self,
+        requirements: Iterable[str],
+        prefix: _Prefix,
+        *,
+        kind: str,
+        for_req: InstallRequirement | None,
+    ) -> None:
+        finder = self.finder
+        args: list[str] = [
+            sys.executable,
+            get_runnable_pip(),
+            "install",
+            "--ignore-installed",
+            "--no-user",
+            "--prefix",
+            prefix.path,
+            "--no-warn-script-location",
+            "--disable-pip-version-check",
+            # As the build environment is ephemeral, it's wasteful to
+            # pre-compile everything, especially as not every Python
+            # module will be used/compiled in most cases.
+            "--no-compile",
+            # The prefix specified two lines above, thus
+            # target from config file or env var should be ignored
+            "--target",
+            "",
+        ]
+        if logger.getEffectiveLevel() <= logging.DEBUG:
+            args.append("-vv")
+        elif logger.getEffectiveLevel() <= VERBOSE:
+            args.append("-v")
+        for format_control in ("no_binary", "only_binary"):
+            formats = getattr(finder.format_control, format_control)
+            args.extend(
+                (
+                    "--" + format_control.replace("_", "-"),
+                    ",".join(sorted(formats or {":none:"})),
+                )
+            )
+
+        index_urls = finder.index_urls
+        if index_urls:
+            args.extend(["-i", index_urls[0]])
+            for extra_index in index_urls[1:]:
+                args.extend(["--extra-index-url", extra_index])
+        else:
+            args.append("--no-index")
+        for link in finder.find_links:
+            args.extend(["--find-links", link])
+
+        if finder.proxy:
+            args.extend(["--proxy", finder.proxy])
+        for host in finder.trusted_hosts:
+            args.extend(["--trusted-host", host])
+        if finder.custom_cert:
+            args.extend(["--cert", finder.custom_cert])
+        if finder.client_cert:
+            args.extend(["--client-cert", finder.client_cert])
+        if finder.allow_all_prereleases:
+            args.append("--pre")
+        if finder.prefer_binary:
+            args.append("--prefer-binary")
+        args.append("--")
+        args.extend(requirements)
+        with open_spinner(f"Installing {kind}") as spinner:
+            call_subprocess(
+                args,
+                command_desc=f"pip subprocess to install {kind}",
+                spinner=spinner,
+            )
+
+
 class BuildEnvironment:
     """Creates and manages an isolated environment to install build deps"""
 
-    def __init__(self) -> None:
+    def __init__(self, installer: BuildEnvironmentInstaller) -> None:
+        self.installer = installer
         temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
 
         self._prefixes = OrderedDict(
@@ -205,96 +304,18 @@ class BuildEnvironment:
 
     def install_requirements(
         self,
-        finder: PackageFinder,
         requirements: Iterable[str],
         prefix_as_string: str,
         *,
         kind: str,
+        for_req: InstallRequirement | None = None,
     ) -> None:
         prefix = self._prefixes[prefix_as_string]
         assert not prefix.setup
         prefix.setup = True
         if not requirements:
             return
-        self._install_requirements(
-            get_runnable_pip(),
-            finder,
-            requirements,
-            prefix,
-            kind=kind,
-        )
-
-    @staticmethod
-    def _install_requirements(
-        pip_runnable: str,
-        finder: PackageFinder,
-        requirements: Iterable[str],
-        prefix: _Prefix,
-        *,
-        kind: str,
-    ) -> None:
-        args: list[str] = [
-            sys.executable,
-            pip_runnable,
-            "install",
-            "--ignore-installed",
-            "--no-user",
-            "--prefix",
-            prefix.path,
-            "--no-warn-script-location",
-            "--disable-pip-version-check",
-            # As the build environment is ephemeral, it's wasteful to
-            # pre-compile everything, especially as not every Python
-            # module will be used/compiled in most cases.
-            "--no-compile",
-            # The prefix specified two lines above, thus
-            # target from config file or env var should be ignored
-            "--target",
-            "",
-        ]
-        if logger.getEffectiveLevel() <= logging.DEBUG:
-            args.append("-vv")
-        elif logger.getEffectiveLevel() <= VERBOSE:
-            args.append("-v")
-        for format_control in ("no_binary", "only_binary"):
-            formats = getattr(finder.format_control, format_control)
-            args.extend(
-                (
-                    "--" + format_control.replace("_", "-"),
-                    ",".join(sorted(formats or {":none:"})),
-                )
-            )
-
-        index_urls = finder.index_urls
-        if index_urls:
-            args.extend(["-i", index_urls[0]])
-            for extra_index in index_urls[1:]:
-                args.extend(["--extra-index-url", extra_index])
-        else:
-            args.append("--no-index")
-        for link in finder.find_links:
-            args.extend(["--find-links", link])
-
-        if finder.proxy:
-            args.extend(["--proxy", finder.proxy])
-        for host in finder.trusted_hosts:
-            args.extend(["--trusted-host", host])
-        if finder.custom_cert:
-            args.extend(["--cert", finder.custom_cert])
-        if finder.client_cert:
-            args.extend(["--client-cert", finder.client_cert])
-        if finder.allow_all_prereleases:
-            args.append("--pre")
-        if finder.prefer_binary:
-            args.append("--prefer-binary")
-        args.append("--")
-        args.extend(requirements)
-        with open_spinner(f"Installing {kind}") as spinner:
-            call_subprocess(
-                args,
-                command_desc=f"pip subprocess to install {kind}",
-                spinner=spinner,
-            )
+        self.installer.install(requirements, prefix, kind=kind, for_req=for_req)
 
 
 class NoOpBuildEnvironment(BuildEnvironment):
@@ -319,10 +340,10 @@ class NoOpBuildEnvironment(BuildEnvironment):
 
     def install_requirements(
         self,
-        finder: PackageFinder,
         requirements: Iterable[str],
         prefix_as_string: str,
         *,
         kind: str,
+        for_req: InstallRequirement | None = None,
     ) -> None:
         raise NotImplementedError()
diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py
index 735bc87ef..7acc29cb3 100644
--- a/src/pip/_internal/cli/base_command.py
+++ b/src/pip/_internal/cli/base_command.py
@@ -176,6 +176,9 @@ class Command(CommandContextMixIn):
         if options.debug_mode:
             self.verbosity = 2
 
+        if hasattr(options, "progress_bar") and options.progress_bar == "auto":
+            options.progress_bar = "on" if self.verbosity >= 0 else "off"
+
         reconfigure(no_color=options.no_color)
         level_number = setup_logging(
             verbosity=self.verbosity,
diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py
index 2476aca80..3519dadf1 100644
--- a/src/pip/_internal/cli/cmdoptions.py
+++ b/src/pip/_internal/cli/cmdoptions.py
@@ -228,9 +228,13 @@ progress_bar: Callable[..., Option] = partial(
     "--progress-bar",
     dest="progress_bar",
     type="choice",
-    choices=["on", "off", "raw"],
-    default="on",
-    help="Specify whether the progress bar should be used [on, off, raw] (default: on)",
+    choices=["auto", "on", "off", "raw"],
+    default="auto",
+    help=(
+        "Specify whether the progress bar should be used. In 'auto'"
+        " mode, --quiet will suppress all progress bars."
+        " [auto, on, off, raw] (default: auto)"
+    ),
 )
 
 log: Callable[..., Option] = partial(
@@ -290,7 +294,7 @@ resume_retries: Callable[..., Option] = partial(
     "--resume-retries",
     dest="resume_retries",
     type="int",
-    default=0,
+    default=5,
     help="Maximum attempts to resume or restart an incomplete download. "
     "(default: %default)",
 )
diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py
index 24cc22ba3..f8b8ac43b 100644
--- a/src/pip/_internal/cli/parser.py
+++ b/src/pip/_internal/cli/parser.py
@@ -188,23 +188,24 @@ class ConfigOptionParser(CustomOptionParser):
         section_items: dict[str, list[tuple[str, Any]]] = {
             name: [] for name in override_order
         }
-        for section_key, val in self.config.items():
-            # ignore empty values
-            if not val:
-                logger.debug(
-                    "Ignoring configuration key '%s' as it's value is empty.",
-                    section_key,
-                )
-                continue
 
-            section, key = section_key.split(".", 1)
-            if section in override_order:
-                section_items[section].append((key, val))
+        for _, value in self.config.items():  # noqa: PERF102
+            for section_key, val in value.items():
+                # ignore empty values
+                if not val:
+                    logger.debug(
+                        "Ignoring configuration key '%s' as its value is empty.",
+                        section_key,
+                    )
+                    continue
+
+                section, key = section_key.split(".", 1)
+                if section in override_order:
+                    section_items[section].append((key, val))
 
-        # Yield each group in their override order
-        for section in override_order:
-            for key, val in section_items[section]:
-                yield key, val
+            # Yield each group in their override order
+            for section in override_order:
+                yield from section_items[section]
 
     def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]:
         """Updates the given defaults with values from the config files and
diff --git a/src/pip/_internal/cli/progress_bars.py b/src/pip/_internal/cli/progress_bars.py
index a1509ac7c..edc47e12c 100644
--- a/src/pip/_internal/cli/progress_bars.py
+++ b/src/pip/_internal/cli/progress_bars.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import functools
 import sys
 from collections.abc import Generator, Iterable, Iterator
-from typing import Callable, TypeVar
+from typing import Callable, Literal, TypeVar
 
 from pip._vendor.rich.progress import (
     BarColumn,
@@ -25,12 +25,13 @@ from pip._internal.utils.logging import get_console, get_indentation
 
 T = TypeVar("T")
 ProgressRenderer = Callable[[Iterable[T]], Iterator[T]]
+BarType = Literal["on", "off", "raw"]
 
 
 def _rich_download_progress_bar(
     iterable: Iterable[bytes],
     *,
-    bar_type: str,
+    bar_type: BarType,
     size: int | None,
     initial_progress: int | None = None,
 ) -> Generator[bytes, None, None]:
@@ -112,7 +113,7 @@ def _raw_progress_bar(
 
 
 def get_download_progress_renderer(
-    *, bar_type: str, size: int | None = None, initial_progress: int | None = None
+    *, bar_type: BarType, size: int | None = None, initial_progress: int | None = None
 ) -> ProgressRenderer[bytes]:
     """Get an object that can be used to render the download progress.
 
@@ -136,7 +137,7 @@ def get_download_progress_renderer(
 
 
 def get_install_progress_renderer(
-    *, bar_type: str, total: int
+    *, bar_type: BarType, total: int
 ) -> ProgressRenderer[InstallRequirement]:
     """Get an object that can be used to render the install progress.
     Returns a callable, that takes an iterable to "wrap".
diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py
index 272550988..dc1328ff0 100644
--- a/src/pip/_internal/cli/req_command.py
+++ b/src/pip/_internal/cli/req_command.py
@@ -12,6 +12,7 @@ from functools import partial
 from optparse import Values
 from typing import Any
 
+from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
 from pip._internal.cache import WheelCache
 from pip._internal.cli import cmdoptions
 from pip._internal.cli.index_command import IndexGroupCommand
@@ -136,6 +137,7 @@ class RequirementCommand(IndexGroupCommand):
             src_dir=options.src_dir,
             download_dir=download_dir,
             build_isolation=options.build_isolation,
+            build_isolation_installer=SubprocessBuildEnvironmentInstaller(finder),
             check_build_deps=options.check_build_deps,
             build_tracker=build_tracker,
             session=session,
diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py
index 1738ed8b0..7bcea0434 100644
--- a/src/pip/_internal/commands/configuration.py
+++ b/src/pip/_internal/commands/configuration.py
@@ -177,7 +177,8 @@ class ConfigurationCommand(Command):
         self._get_n_args(args, "list", n=0)
 
         for key, value in sorted(self.configuration.items()):
-            write_output("%s=%r", key, value)
+            for key, value in sorted(value.items()):
+                write_output("%s=%r", key, value)
 
     def get_name(self, options: Values, args: list[str]) -> None:
         key = self._get_n_args(args, "get [name]", n=1)
@@ -211,13 +212,15 @@ class ConfigurationCommand(Command):
                     file_exists = os.path.exists(fname)
                     write_output("%s, exists: %r", fname, file_exists)
                     if file_exists:
-                        self.print_config_file_values(variant)
+                        self.print_config_file_values(variant, fname)
 
-    def print_config_file_values(self, variant: Kind) -> None:
+    def print_config_file_values(self, variant: Kind, fname: str) -> None:
         """Get key-value pairs from the file of a variant"""
         for name, value in self.configuration.get_values_in_config(variant).items():
             with indent_log():
-                write_output("%s: %s", name, value)
+                if name == fname:
+                    for confname, confvalue in value.items():
+                        write_output("%s: %s", confname, confvalue)
 
     def print_env_var_values(self) -> None:
         """Get key-values pairs present as environment variables"""
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index 302bc63f6..056eb8866 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -27,7 +27,11 @@ from pip._internal.cli.req_command import (
     with_cleanup,
 )
 from pip._internal.cli.status_codes import ERROR, SUCCESS
-from pip._internal.exceptions import CommandError, InstallationError
+from pip._internal.exceptions import (
+    CommandError,
+    InstallationError,
+    InstallWheelBuildError,
+)
 from pip._internal.locations import get_scheme
 from pip._internal.metadata import get_environment
 from pip._internal.models.installation_report import InstallationReport
@@ -434,12 +438,7 @@ class InstallCommand(RequirementCommand):
             )
 
             if build_failures:
-                raise InstallationError(
-                    "Failed to build installable wheels for some "
-                    "pyproject.toml based projects ({})".format(
-                        ", ".join(r.name for r in build_failures)  # type: ignore
-                    )
-                )
+                raise InstallWheelBuildError(build_failures)
 
             to_install = resolver.get_installation_order(requirement_set)
 
diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py
index 6902f66ab..f581a2c95 100644
--- a/src/pip/_internal/configuration.py
+++ b/src/pip/_internal/configuration.py
@@ -117,7 +117,7 @@ class Configuration:
         self._parsers: dict[Kind, list[tuple[str, RawConfigParser]]] = {
             variant: [] for variant in OVERRIDE_ORDER
         }
-        self._config: dict[Kind, dict[str, Any]] = {
+        self._config: dict[Kind, dict[str, dict[str, Any]]] = {
             variant: {} for variant in OVERRIDE_ORDER
         }
         self._modified_parsers: list[tuple[str, RawConfigParser]] = []
@@ -148,7 +148,10 @@ class Configuration:
         orig_key = key
         key = _normalize_name(key)
         try:
-            return self._dictionary[key]
+            clean_config: dict[str, Any] = {}
+            for file_values in self._dictionary.values():
+                clean_config.update(file_values)
+            return clean_config[key]
         except KeyError:
             # disassembling triggers a more useful error message than simply
             # "No such key" in the case that the key isn't in the form command.option
@@ -171,7 +174,8 @@ class Configuration:
                 parser.add_section(section)
             parser.set(section, name, value)
 
-        self._config[self.load_only][key] = value
+        self._config[self.load_only].setdefault(fname, {})
+        self._config[self.load_only][fname][key] = value
         self._mark_as_modified(fname, parser)
 
     def unset_value(self, key: str) -> None:
@@ -181,11 +185,14 @@ class Configuration:
         self._ensure_have_load_only()
 
         assert self.load_only
-        if key not in self._config[self.load_only]:
-            raise ConfigurationError(f"No such key - {orig_key}")
-
         fname, parser = self._get_parser_to_modify()
 
+        if (
+            key not in self._config[self.load_only][fname]
+            and key not in self._config[self.load_only]
+        ):
+            raise ConfigurationError(f"No such key - {orig_key}")
+
         if parser is not None:
             section, name = _disassemble_key(key)
             if not (
@@ -200,8 +207,10 @@ class Configuration:
             if not parser.items(section):
                 parser.remove_section(section)
             self._mark_as_modified(fname, parser)
-
-        del self._config[self.load_only][key]
+        try:
+            del self._config[self.load_only][fname][key]
+        except KeyError:
+            del self._config[self.load_only][key]
 
     def save(self) -> None:
         """Save the current in-memory state."""
@@ -233,7 +242,7 @@ class Configuration:
         logger.debug("Will be working with %s variant only", self.load_only)
 
     @property
-    def _dictionary(self) -> dict[str, Any]:
+    def _dictionary(self) -> dict[str, dict[str, Any]]:
         """A dictionary representing the loaded configuration."""
         # NOTE: Dictionaries are not populated if not loaded. So, conditionals
         #       are not needed here.
@@ -273,7 +282,8 @@ class Configuration:
 
         for section in parser.sections():
             items = parser.items(section)
-            self._config[variant].update(self._normalized_keys(section, items))
+            self._config[variant].setdefault(fname, {})
+            self._config[variant][fname].update(self._normalized_keys(section, items))
 
         return parser
 
@@ -300,7 +310,8 @@ class Configuration:
 
     def _load_environment_vars(self) -> None:
         """Loads configuration from environment variables"""
-        self._config[kinds.ENV_VAR].update(
+        self._config[kinds.ENV_VAR].setdefault(":env:", {})
+        self._config[kinds.ENV_VAR][":env:"].update(
             self._normalized_keys(":env:", self.get_environ_vars())
         )
 
diff --git a/src/pip/_internal/distributions/base.py b/src/pip/_internal/distributions/base.py
index 6ebf49ad5..ea61f3501 100644
--- a/src/pip/_internal/distributions/base.py
+++ b/src/pip/_internal/distributions/base.py
@@ -7,7 +7,7 @@ from pip._internal.metadata.base import BaseDistribution
 from pip._internal.req import InstallRequirement
 
 if TYPE_CHECKING:
-    from pip._internal.index.package_finder import PackageFinder
+    from pip._internal.build_env import BuildEnvironmentInstaller
 
 
 class AbstractDistribution(metaclass=abc.ABCMeta):
@@ -48,7 +48,7 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
     @abc.abstractmethod
     def prepare_distribution_metadata(
         self,
-        finder: PackageFinder,
+        build_env_installer: BuildEnvironmentInstaller,
         build_isolation: bool,
         check_build_deps: bool,
     ) -> None:
diff --git a/src/pip/_internal/distributions/installed.py b/src/pip/_internal/distributions/installed.py
index 8ceed3094..b6a67df24 100644
--- a/src/pip/_internal/distributions/installed.py
+++ b/src/pip/_internal/distributions/installed.py
@@ -1,9 +1,13 @@
 from __future__ import annotations
 
+from typing import TYPE_CHECKING
+
 from pip._internal.distributions.base import AbstractDistribution
-from pip._internal.index.package_finder import PackageFinder
 from pip._internal.metadata import BaseDistribution
 
+if TYPE_CHECKING:
+    from pip._internal.build_env import BuildEnvironmentInstaller
+
 
 class InstalledDistribution(AbstractDistribution):
     """Represents an installed package.
@@ -22,7 +26,7 @@ class InstalledDistribution(AbstractDistribution):
 
     def prepare_distribution_metadata(
         self,
-        finder: PackageFinder,
+        build_env_installer: BuildEnvironmentInstaller,
         build_isolation: bool,
         check_build_deps: bool,
     ) -> None:
diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py
index 67468955e..e2821f89e 100644
--- a/src/pip/_internal/distributions/sdist.py
+++ b/src/pip/_internal/distributions/sdist.py
@@ -11,7 +11,7 @@ from pip._internal.metadata import BaseDistribution
 from pip._internal.utils.subprocess import runner_with_spinner_message
 
 if TYPE_CHECKING:
-    from pip._internal.index.package_finder import PackageFinder
+    from pip._internal.build_env import BuildEnvironmentInstaller
 
 logger = logging.getLogger(__name__)
 
@@ -34,7 +34,7 @@ class SourceDistribution(AbstractDistribution):
 
     def prepare_distribution_metadata(
         self,
-        finder: PackageFinder,
+        build_env_installer: BuildEnvironmentInstaller,
         build_isolation: bool,
         check_build_deps: bool,
     ) -> None:
@@ -46,7 +46,7 @@ class SourceDistribution(AbstractDistribution):
         if should_isolate:
             # Setup an isolated environment and install the build backend static
             # requirements in it.
-            self._prepare_build_backend(finder)
+            self._prepare_build_backend(build_env_installer)
             # Check that if the requirement is editable, it either supports PEP 660 or
             # has a setup.py or a setup.cfg. This cannot be done earlier because we need
             # to setup the build backend to verify it supports build_editable, nor can
@@ -56,7 +56,7 @@ class SourceDistribution(AbstractDistribution):
             # without setup.py nor setup.cfg.
             self.req.isolated_editable_sanity_check()
             # Install the dynamic build requirements.
-            self._install_build_reqs(finder)
+            self._install_build_reqs(build_env_installer)
         # Check if the current environment provides build dependencies
         should_check_deps = self.req.use_pep517 and check_build_deps
         if should_check_deps:
@@ -71,15 +71,17 @@ class SourceDistribution(AbstractDistribution):
                 self._raise_missing_reqs(missing)
         self.req.prepare_metadata()
 
-    def _prepare_build_backend(self, finder: PackageFinder) -> None:
+    def _prepare_build_backend(
+        self, build_env_installer: BuildEnvironmentInstaller
+    ) -> None:
         # Isolate in a BuildEnvironment and install the build-time
         # requirements.
         pyproject_requires = self.req.pyproject_requires
         assert pyproject_requires is not None
 
-        self.req.build_env = BuildEnvironment()
+        self.req.build_env = BuildEnvironment(build_env_installer)
         self.req.build_env.install_requirements(
-            finder, pyproject_requires, "overlay", kind="build dependencies"
+            pyproject_requires, "overlay", kind="build dependencies", for_req=self.req
         )
         conflicting, missing = self.req.build_env.check_requirements(
             self.req.requirements_to_check
@@ -115,7 +117,9 @@ class SourceDistribution(AbstractDistribution):
             with backend.subprocess_runner(runner):
                 return backend.get_requires_for_build_editable()
 
-    def _install_build_reqs(self, finder: PackageFinder) -> None:
+    def _install_build_reqs(
+        self, build_env_installer: BuildEnvironmentInstaller
+    ) -> None:
         # Install any extra build dependencies that the backend requests.
         # This must be done in a second pass, as the pyproject.toml
         # dependencies must be installed before we can call the backend.
@@ -131,7 +135,7 @@ class SourceDistribution(AbstractDistribution):
         if conflicting:
             self._raise_conflicts("the backend dependencies", conflicting)
         self.req.build_env.install_requirements(
-            finder, missing, "normal", kind="backend dependencies"
+            missing, "normal", kind="backend dependencies", for_req=self.req
         )
 
     def _raise_conflicts(
diff --git a/src/pip/_internal/distributions/wheel.py b/src/pip/_internal/distributions/wheel.py
index 54e1e35f2..ee12bfadc 100644
--- a/src/pip/_internal/distributions/wheel.py
+++ b/src/pip/_internal/distributions/wheel.py
@@ -12,7 +12,7 @@ from pip._internal.metadata import (
 )
 
 if TYPE_CHECKING:
-    from pip._internal.index.package_finder import PackageFinder
+    from pip._internal.build_env import BuildEnvironmentInstaller
 
 
 class WheelDistribution(AbstractDistribution):
@@ -37,7 +37,7 @@ class WheelDistribution(AbstractDistribution):
 
     def prepare_distribution_metadata(
         self,
-        finder: PackageFinder,
+        build_env_installer: BuildEnvironmentInstaller,
         build_isolation: bool,
         check_build_deps: bool,
     ) -> None:
diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py
index 0661897e9..98f95494c 100644
--- a/src/pip/_internal/exceptions.py
+++ b/src/pip/_internal/exceptions.py
@@ -865,3 +865,17 @@ class ResolutionTooDeepError(DiagnosticPipError):
             ),
             link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors",
         )
+
+
+class InstallWheelBuildError(DiagnosticPipError):
+    reference = "failed-wheel-build-for-install"
+
+    def __init__(self, failed: list[InstallRequirement]) -> None:
+        super().__init__(
+            message=(
+                "Failed to build installable wheels for some "
+                "pyproject.toml based projects"
+            ),
+            context=", ".join(r.name for r in failed),  # type: ignore
+            hint_stmt=None,
+        )
diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py
index a655c19c3..9881cc285 100644
--- a/src/pip/_internal/network/download.py
+++ b/src/pip/_internal/network/download.py
@@ -17,7 +17,7 @@ from pip._vendor.urllib3 import HTTPResponse as URLlib3Response
 from pip._vendor.urllib3._collections import HTTPHeaderDict
 from pip._vendor.urllib3.exceptions import ReadTimeoutError
 
-from pip._internal.cli.progress_bars import get_download_progress_renderer
+from pip._internal.cli.progress_bars import BarType, get_download_progress_renderer
 from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError
 from pip._internal.models.index import PyPI
 from pip._internal.models.link import Link
@@ -47,7 +47,7 @@ def _get_http_response_etag_or_last_modified(resp: Response) -> str | None:
 def _log_download(
     resp: Response,
     link: Link,
-    progress_bar: str,
+    progress_bar: BarType,
     total_length: int | None,
     range_start: int | None = 0,
 ) -> Iterable[bytes]:
@@ -166,7 +166,7 @@ class Downloader:
     def __init__(
         self,
         session: PipSession,
-        progress_bar: str,
+        progress_bar: BarType,
         resume_retries: int,
     ) -> None:
         assert (
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index 908331eba..00b1a33a0 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -10,9 +10,11 @@ import shutil
 from collections.abc import Iterable
 from dataclasses import dataclass
 from pathlib import Path
+from typing import TYPE_CHECKING
 
 from pip._vendor.packaging.utils import canonicalize_name
 
+from pip._internal.build_env import BuildEnvironmentInstaller
 from pip._internal.distributions import make_distribution_for_install_requirement
 from pip._internal.distributions.installed import InstalledDistribution
 from pip._internal.exceptions import (
@@ -54,13 +56,16 @@ from pip._internal.utils.temp_dir import TempDirectory
 from pip._internal.utils.unpacking import unpack_file
 from pip._internal.vcs import vcs
 
+if TYPE_CHECKING:
+    from pip._internal.cli.progress_bars import BarType
+
 logger = getLogger(__name__)
 
 
 def _get_prepared_distribution(
     req: InstallRequirement,
     build_tracker: BuildTracker,
-    finder: PackageFinder,
+    build_env_installer: BuildEnvironmentInstaller,
     build_isolation: bool,
     check_build_deps: bool,
 ) -> BaseDistribution:
@@ -70,7 +75,7 @@ def _get_prepared_distribution(
     if tracker_id is not None:
         with build_tracker.track(req, tracker_id):
             abstract_dist.prepare_distribution_metadata(
-                finder, build_isolation, check_build_deps
+                build_env_installer, build_isolation, check_build_deps
             )
     return abstract_dist.get_metadata_distribution()
 
@@ -220,16 +225,18 @@ def _check_download_dir(
 class RequirementPreparer:
     """Prepares a Requirement"""
 
-    def __init__(
+    def __init__(  # noqa: PLR0913 (too many parameters)
         self,
+        *,
         build_dir: str,
         download_dir: str | None,
         src_dir: str,
         build_isolation: bool,
+        build_isolation_installer: BuildEnvironmentInstaller,
         check_build_deps: bool,
         build_tracker: BuildTracker,
         session: PipSession,
-        progress_bar: str,
+        progress_bar: BarType,
         finder: PackageFinder,
         require_hashes: bool,
         use_user_site: bool,
@@ -253,6 +260,7 @@ class RequirementPreparer:
 
         # Is build isolation allowed?
         self.build_isolation = build_isolation
+        self.build_env_installer = build_isolation_installer
 
         # Should check build dependencies?
         self.check_build_deps = check_build_deps
@@ -644,7 +652,7 @@ class RequirementPreparer:
         dist = _get_prepared_distribution(
             req,
             self.build_tracker,
-            self.finder,
+            self.build_env_installer,
             self.build_isolation,
             self.check_build_deps,
         )
@@ -700,7 +708,7 @@ class RequirementPreparer:
             dist = _get_prepared_distribution(
                 req,
                 self.build_tracker,
-                self.finder,
+                self.build_env_installer,
                 self.build_isolation,
                 self.check_build_deps,
             )
diff --git a/src/pip/_internal/req/__init__.py b/src/pip/_internal/req/__init__.py
index 956c1f2fa..e5050ee58 100644
--- a/src/pip/_internal/req/__init__.py
+++ b/src/pip/_internal/req/__init__.py
@@ -5,7 +5,7 @@ import logging
 from collections.abc import Generator, Sequence
 from dataclasses import dataclass
 
-from pip._internal.cli.progress_bars import get_install_progress_renderer
+from pip._internal.cli.progress_bars import BarType, get_install_progress_renderer
 from pip._internal.utils.logging import indent_log
 
 from .req_file import parse_requirements
@@ -44,7 +44,7 @@ def install_given_reqs(
     warn_script_location: bool,
     use_user_site: bool,
     pycompile: bool,
-    progress_bar: str,
+    progress_bar: BarType,
 ) -> list[InstallationResult]:
     """
     Install everything in the given list.
diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py
index 87feaf128..1ba70c2b3 100644
--- a/src/pip/_internal/resolution/resolvelib/resolver.py
+++ b/src/pip/_internal/resolution/resolvelib/resolver.py
@@ -248,13 +248,24 @@ def get_topological_weights(
     requirement_keys.
     """
     path: set[str | None] = set()
-    weights: dict[str | None, int] = {}
+    weights: dict[str | None, list[int]] = {}
 
     def visit(node: str | None) -> None:
         if node in path:
             # We hit a cycle, so we'll break it here.
             return
 
+        # The walk is exponential and for pathologically connected graphs (which
+        # are the ones most likely to contain cycles in the first place) it can
+        # take until the heat-death of the universe. To counter this we limit
+        # the number of attempts to visit (i.e. traverse through) any given
+        # node. We choose a value here which gives decent enough coverage for
+        # fairly well behaved graphs, and still limits the walk complexity to be
+        # linear in nature.
+        cur_weights = weights.get(node, [])
+        if len(cur_weights) >= 5:
+            return
+
         # Time to visit the children!
         path.add(node)
         for child in graph.iter_children(node):
@@ -264,14 +275,14 @@ def get_topological_weights(
         if node not in requirement_keys:
             return
 
-        last_known_parent_count = weights.get(node, 0)
-        weights[node] = max(last_known_parent_count, len(path))
+        cur_weights.append(len(path))
+        weights[node] = cur_weights
 
-    # Simplify the graph, pruning leaves that have no dependencies.
-    # This is needed for large graphs (say over 200 packages) because the
-    # `visit` function is exponentially slower then, taking minutes.
+    # Simplify the graph, pruning leaves that have no dependencies. This is
+    # needed for large graphs (say over 200 packages) because the `visit`
+    # function is slower for large/densely connected graphs, taking minutes.
     # See https://github.com/pypa/pip/issues/10557
-    # We will loop until we explicitly break the loop.
+    # We repeat the pruning step until we have no more leaves to remove.
     while True:
         leaves = set()
         for key in graph:
@@ -291,12 +302,13 @@ def get_topological_weights(
         for leaf in leaves:
             if leaf not in requirement_keys:
                 continue
-            weights[leaf] = weight
+            weights[leaf] = [weight]
         # Remove the leaves from the graph, making it simpler.
         for leaf in leaves:
             graph.remove(leaf)
 
-    # Visit the remaining graph.
+    # Visit the remaining graph, this will only have nodes to handle if the
+    # graph had a cycle in it, which the pruning step above could not handle.
     # `None` is guaranteed to be the root node by resolvelib.
     visit(None)
 
@@ -305,7 +317,9 @@ def get_topological_weights(
     difference = set(weights.keys()).difference(requirement_keys)
     assert not difference, difference
 
-    return weights
+    # Now give back all the weights, choosing the largest ones from what we
+    # accumulated.
+    return {node: max(wgts) for (node, wgts) in weights.items()}
 
 
 def _req_set_item_sorter(
diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py
index 34fcf9713..1769da791 100644
--- a/src/pip/_internal/vcs/git.py
+++ b/src/pip/_internal/vcs/git.py
@@ -236,7 +236,7 @@ class Git(VersionControl):
         # Do not show a warning for the common case of something that has
         # the form of a Git commit hash.
         if not looks_like_hash(rev):
-            logger.warning(
+            logger.info(
                 "Did not find branch or tag '%s', assuming revision or ref.",
                 rev,
             )


Test patch:
diff --git a/tests/functional/test_build_env.py b/tests/functional/test_build_env.py
index 1a1839b40..ec1b59f06 100644
--- a/tests/functional/test_build_env.py
+++ b/tests/functional/test_build_env.py
@@ -6,7 +6,11 @@ from textwrap import dedent
 
 import pytest
 
-from pip._internal.build_env import BuildEnvironment, _get_system_sitepackages
+from pip._internal.build_env import (
+    BuildEnvironment,
+    SubprocessBuildEnvironmentInstaller,
+    _get_system_sitepackages,
+)
 
 from tests.lib import (
     PipTestEnvironment,
@@ -33,7 +37,10 @@ def run_with_build_env(
             import subprocess
             import sys
 
-            from pip._internal.build_env import BuildEnvironment
+            from pip._internal.build_env import (
+                BuildEnvironment,
+                SubprocessBuildEnvironmentInstaller,
+            )
             from pip._internal.index.collector import LinkCollector
             from pip._internal.index.package_finder import PackageFinder
             from pip._internal.models.search_scope import SearchScope
@@ -56,7 +63,9 @@ def run_with_build_env(
             )
 
             with global_tempdir_manager():
-                build_env = BuildEnvironment()
+                build_env = BuildEnvironment(
+                    SubprocessBuildEnvironmentInstaller(finder)
+                )
             """
         )
         + indent(dedent(setup_script_contents), "    ")
@@ -81,30 +90,26 @@ def run_with_build_env(
 
 def test_build_env_allow_empty_requirements_install() -> None:
     finder = make_test_finder()
-    build_env = BuildEnvironment()
+    build_env = BuildEnvironment(SubprocessBuildEnvironmentInstaller(finder))
     for prefix in ("normal", "overlay"):
-        build_env.install_requirements(
-            finder, [], prefix, kind="Installing build dependencies"
-        )
+        build_env.install_requirements([], prefix, kind="Installing build dependencies")
 
 
 def test_build_env_allow_only_one_install(script: PipTestEnvironment) -> None:
     create_basic_wheel_for_package(script, "foo", "1.0")
     create_basic_wheel_for_package(script, "bar", "1.0")
     finder = make_test_finder(find_links=[os.fspath(script.scratch_path)])
-    build_env = BuildEnvironment()
+    build_env = BuildEnvironment(SubprocessBuildEnvironmentInstaller(finder))
     for prefix in ("normal", "overlay"):
         build_env.install_requirements(
-            finder, ["foo"], prefix, kind=f"installing foo in {prefix}"
+            ["foo"], prefix, kind=f"installing foo in {prefix}"
         )
         with pytest.raises(AssertionError):
             build_env.install_requirements(
-                finder, ["bar"], prefix, kind=f"installing bar in {prefix}"
+                ["bar"], prefix, kind=f"installing bar in {prefix}"
             )
         with pytest.raises(AssertionError):
-            build_env.install_requirements(
-                finder, [], prefix, kind=f"installing in {prefix}"
-            )
+            build_env.install_requirements([], prefix, kind=f"installing in {prefix}")
 
 
 def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
@@ -132,7 +137,7 @@ def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
     run_with_build_env(
         script,
         """
-        build_env.install_requirements(finder, ['foo', 'bar==3.0'], 'normal',
+        build_env.install_requirements(['foo', 'bar==3.0'], 'normal',
                                        kind='installing foo in normal')
 
         r = build_env.check_requirements(['foo', 'bar', 'other'])
@@ -149,9 +154,9 @@ def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
     run_with_build_env(
         script,
         """
-        build_env.install_requirements(finder, ['foo', 'bar==3.0'], 'normal',
+        build_env.install_requirements(['foo', 'bar==3.0'], 'normal',
                                        kind='installing foo in normal')
-        build_env.install_requirements(finder, ['bar==1.0'], 'overlay',
+        build_env.install_requirements(['bar==1.0'], 'overlay',
                                        kind='installing foo in overlay')
 
         r = build_env.check_requirements(['foo', 'bar', 'other'])
@@ -170,7 +175,6 @@ def test_build_env_requirements_check(script: PipTestEnvironment) -> None:
         script,
         """
         build_env.install_requirements(
-            finder,
             ["bar==3.0"],
             "normal",
             kind="installing bar in normal",
@@ -193,9 +197,9 @@ def test_build_env_overlay_prefix_has_priority(script: PipTestEnvironment) -> No
     result = run_with_build_env(
         script,
         """
-        build_env.install_requirements(finder, ['pkg==2.0'], 'overlay',
+        build_env.install_requirements(['pkg==2.0'], 'overlay',
                                        kind='installing pkg==2.0 in overlay')
-        build_env.install_requirements(finder, ['pkg==4.3'], 'normal',
+        build_env.install_requirements(['pkg==4.3'], 'normal',
                                        kind='installing pkg==4.3 in normal')
         """,
         """
diff --git a/tests/functional/test_configuration.py b/tests/functional/test_configuration.py
index f049adf6b..af3000e7f 100644
--- a/tests/functional/test_configuration.py
+++ b/tests/functional/test_configuration.py
@@ -1,16 +1,43 @@
 """Tests for the config command"""
 
+from __future__ import annotations
+
+import ast
+import os
 import re
+import subprocess
+import sys
 import textwrap
 
 from pip._internal.cli.status_codes import ERROR
-from pip._internal.configuration import CONFIG_BASENAME, get_configuration_files
+from pip._internal.configuration import CONFIG_BASENAME, Kind
+from pip._internal.configuration import get_configuration_files as _get_config_files
+from pip._internal.utils.compat import WINDOWS
 
 from tests.lib import PipTestEnvironment
 from tests.lib.configuration_helpers import ConfigurationMixin, kinds
 from tests.lib.venv import VirtualEnvironment
 
 
+def get_configuration_files() -> dict[Kind, list[str]]:
+    """Wrapper over pip._internal.configuration.get_configuration_files()."""
+    if WINDOWS:
+        # The user configuration directory is updated in the isolate fixture using the
+        # APPDATA environment variable. This will only take effect in new subprocesses,
+        # however. To ensure that get_configuration_files() never returns stale data,
+        # call it in a subprocess on Windows.
+        code = (
+            "from pip._internal.configuration import get_configuration_files; "
+            "print(get_configuration_files())"
+        )
+        proc = subprocess.run(
+            [sys.executable, "-c", code], capture_output=True, encoding="utf-8"
+        )
+        return ast.literal_eval(proc.stdout)
+    else:
+        return _get_config_files()
+
+
 def test_no_options_passed_should_error(script: PipTestEnvironment) -> None:
     result = script.pip("config", expect_error=True)
     assert result.returncode == ERROR
@@ -148,3 +175,40 @@ class TestBasicLoading(ConfigurationMixin):
             "config", "edit", "--editor", "notrealeditor", expect_error=True
         )
         assert "notrealeditor" in result.stderr
+
+    def test_config_separated(
+        self, script: PipTestEnvironment, virtualenv: VirtualEnvironment
+    ) -> None:
+        """Test that the pip configuration values in the different config sections
+        are correctly assigned to their origin files.
+        """
+
+        # Use new config file
+        new_config_file = get_configuration_files()[kinds.USER][1]
+
+        # Get legacy config file and touch it for testing purposes
+        legacy_config_file = get_configuration_files()[kinds.USER][0]
+        os.makedirs(os.path.dirname(legacy_config_file))
+        open(legacy_config_file, "a").close()
+
+        # Site config file
+        site_config_file = virtualenv.location / CONFIG_BASENAME
+
+        script.pip("config", "--user", "set", "global.timeout", "60")
+        script.pip("config", "--site", "set", "freeze.timeout", "10")
+
+        result = script.pip("config", "debug")
+
+        assert (
+            f"{site_config_file}, exists: True\n    freeze.timeout: 10" in result.stdout
+        )
+        assert (
+            f"{new_config_file}, exists: True\n    global.timeout: 60" in result.stdout
+        )
+        assert re.search(
+            (
+                rf"{re.escape(legacy_config_file)}, "
+                rf"exists: True\n(  {re.escape(new_config_file)}.+\n)+"
+            ),
+            result.stdout,
+        )
diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py
index 29606075a..baee7e80d 100644
--- a/tests/functional/test_install_config.py
+++ b/tests/functional/test_install_config.py
@@ -539,3 +539,14 @@ def test_prompt_for_keyring_if_needed(
         assert function_name + " was called" in result.stderr
     else:
         assert function_name + " was called" not in result.stderr
+
+
+@pytest.mark.network
+def test_install_quiet_log(script: PipTestEnvironment, data: TestData) -> None:
+    """
+    Test suppressing the progress bar with --quiet and --log.
+    """
+    logfile = script.scratch_path / "log"
+    result = script.pip("install", "-qqq", "setuptools==62.0.0", "--log", logfile)
+    assert result.stdout == ""
+    assert result.stderr == ""
diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py
index 9be383424..47dae9681 100644
--- a/tests/functional/test_pep517.py
+++ b/tests/functional/test_pep517.py
@@ -7,7 +7,10 @@ from typing import Any
 import pytest
 import tomli_w
 
-from pip._internal.build_env import BuildEnvironment
+from pip._internal.build_env import (
+    BuildEnvironment,
+    SubprocessBuildEnvironmentInstaller,
+)
 from pip._internal.req import InstallRequirement
 
 from tests.lib import (
@@ -43,9 +46,9 @@ def test_backend(tmpdir: Path, data: TestData) -> None:
     req = InstallRequirement(None, None)
     req.source_dir = os.fspath(project_dir)  # make req believe it has been unpacked
     req.load_pyproject_toml()
-    env = BuildEnvironment()
     finder = make_test_finder(find_links=[data.backends])
-    env.install_requirements(finder, ["dummy_backend"], "normal", kind="Installing")
+    env = BuildEnvironment(SubprocessBuildEnvironmentInstaller(finder))
+    env.install_requirements(["dummy_backend"], "normal", kind="Installing")
     conflicting, missing = env.check_requirements(["dummy_backend"])
     assert not conflicting
     assert not missing
@@ -73,7 +76,7 @@ def test_backend_path(tmpdir: Path, data: TestData) -> None:
     req.source_dir = os.fspath(project_dir)  # make req believe it has been unpacked
     req.load_pyproject_toml()
 
-    env = BuildEnvironment()
+    env = BuildEnvironment(object())  # type: ignore
     assert hasattr(req.pep517_backend, "build_wheel")
     with env:
         assert req.pep517_backend is not None
@@ -91,9 +94,9 @@ def test_backend_path_and_dep(tmpdir: Path, data: TestData) -> None:
     req = InstallRequirement(None, None)
     req.source_dir = os.fspath(project_dir)  # make req believe it has been unpacked
     req.load_pyproject_toml()
-    env = BuildEnvironment()
     finder = make_test_finder(find_links=[data.backends])
-    env.install_requirements(finder, ["dummy_backend"], "normal", kind="Installing")
+    env = BuildEnvironment(SubprocessBuildEnvironmentInstaller(finder))
+    env.install_requirements(["dummy_backend"], "normal", kind="Installing")
 
     assert hasattr(req.pep517_backend, "build_wheel")
     with env:
diff --git a/tests/lib/configuration_helpers.py b/tests/lib/configuration_helpers.py
index 2826f2ee1..e4dc2323f 100644
--- a/tests/lib/configuration_helpers.py
+++ b/tests/lib/configuration_helpers.py
@@ -28,7 +28,8 @@ class ConfigurationMixin:
         @functools.wraps(old)
         def overridden() -> None:
             # Manual Overload
-            self.configuration._config[variant].update(di)
+            self.configuration._config[variant].setdefault("fakefile", {})
+            self.configuration._config[variant]["fakefile"].update(di)
             # Configuration._parsers has type:
             # Dict[Kind, List[Tuple[str, RawConfigParser]]].
             # As a testing convenience, pass a special value.
diff --git a/tests/requirements-common_wheels.txt b/tests/requirements-common_wheels.txt
deleted file mode 100644
index 31450a0e8..000000000
--- a/tests/requirements-common_wheels.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-# Create local setuptools wheel files for testing by:
-# 1. Cloning setuptools and checking out the branch of interest
-# 2. Running `python3 bootstrap.py` in that directory
-# 3. Running `python3 -m pip wheel --no-cache -w /tmp/setuptools_build_meta_legacy/ .`
-# 4. Replacing the `setuptools` entry below with a `file:///...` URL
-# (Adjust artifact directory used based on preference and operating system)
-
-# We pin setuptools<80 because our test suite currently
-# depends on setup.py develop to generate egg-link files.
-setuptools >= 40.8.0, != 60.6.0, <80
-wheel
-# As required by pytest-cov.
-coverage >= 4.4
diff --git a/tests/requirements.txt b/tests/requirements.txt
deleted file mode 100644
index 0568aea51..000000000
--- a/tests/requirements.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-cryptography
-freezegun
-installer
-pytest
-pytest-cov
-pytest-rerunfailures
-pytest-xdist
-scripttest
-setuptools
-# macOS (darwin) arm64 always uses virtualenv >= 20.0
-# for other platforms, it depends on python version
-virtualenv < 20.0 ; python_version < '3.10' and (sys_platform != 'darwin' or platform_machine != 'arm64')
-virtualenv >= 20.0 ; python_version >= '3.10' or (sys_platform == 'darwin' and platform_machine == 'arm64')
-werkzeug
-wheel
-tomli-w
-proxy.py
diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py
index ab9b8ea71..054713113 100644
--- a/tests/unit/test_req.py
+++ b/tests/unit/test_req.py
@@ -17,6 +17,7 @@ import pytest
 from pip._vendor.packaging.markers import Marker
 from pip._vendor.packaging.requirements import Requirement
 
+from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
 from pip._internal.cache import WheelCache
 from pip._internal.commands import create_command
 from pip._internal.commands.install import InstallCommand
@@ -97,11 +98,13 @@ class TestRequirementSet:
         session = PipSession()
 
         with get_build_tracker() as tracker:
+            installer = SubprocessBuildEnvironmentInstaller(finder)
             preparer = RequirementPreparer(
                 build_dir=os.path.join(self.tempdir, "build"),
                 src_dir=os.path.join(self.tempdir, "src"),
                 download_dir=None,
                 build_isolation=True,
+                build_isolation_installer=installer,
                 check_build_deps=False,
                 build_tracker=tracker,
                 session=session,
diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py
index 3726591ea..93f68fd7b 100644
--- a/tests/unit/test_vcs.py
+++ b/tests/unit/test_vcs.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import logging
 import os
 import pathlib
 from typing import Any
@@ -271,6 +272,7 @@ def test_git_resolve_revision_rev_not_found(get_sha_mock: mock.Mock) -> None:
 def test_git_resolve_revision_not_found_warning(
     get_sha_mock: mock.Mock, caplog: pytest.LogCaptureFixture
 ) -> None:
+    caplog.set_level(logging.INFO)
     get_sha_mock.return_value = (None, False)
     url = HiddenText("git+https://git.example.com", redacted="*")
     sha = 40 * "a"