python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_internal/
pyproject.py
       1  import importlib.util
       2  import os
       3  from collections import namedtuple
       4  from typing import Any, List, Optional
       5  
       6  from pip._vendor import tomli
       7  from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
       8  
       9  from pip._internal.exceptions import (
      10      InstallationError,
      11      InvalidPyProjectBuildRequires,
      12      MissingPyProjectBuildRequires,
      13  )
      14  
      15  
      16  def _is_list_of_str(obj: Any) -> bool:
      17      return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
      18  
      19  
      20  def make_pyproject_path(unpacked_source_directory: str) -> str:
      21      return os.path.join(unpacked_source_directory, "pyproject.toml")
      22  
      23  
      24  BuildSystemDetails = namedtuple(
      25      "BuildSystemDetails", ["requires", "backend", "check", "backend_path"]
      26  )
      27  
      28  
      29  def load_pyproject_toml(
      30      use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
      31  ) -> Optional[BuildSystemDetails]:
      32      """Load the pyproject.toml file.
      33  
      34      Parameters:
      35          use_pep517 - Has the user requested PEP 517 processing? None
      36                       means the user hasn't explicitly specified.
      37          pyproject_toml - Location of the project's pyproject.toml file
      38          setup_py - Location of the project's setup.py file
      39          req_name - The name of the requirement we're processing (for
      40                     error reporting)
      41  
      42      Returns:
      43          None if we should use the legacy code path, otherwise a tuple
      44          (
      45              requirements from pyproject.toml,
      46              name of PEP 517 backend,
      47              requirements we should check are installed after setting
      48                  up the build environment
      49              directory paths to import the backend from (backend-path),
      50                  relative to the project root.
      51          )
      52      """
      53      has_pyproject = os.path.isfile(pyproject_toml)
      54      has_setup = os.path.isfile(setup_py)
      55  
      56      if not has_pyproject and not has_setup:
      57          raise InstallationError(
      58              f"{req_name} does not appear to be a Python project: "
      59              f"neither 'setup.py' nor 'pyproject.toml' found."
      60          )
      61  
      62      if has_pyproject:
      63          with open(pyproject_toml, encoding="utf-8") as f:
      64              pp_toml = tomli.loads(f.read())
      65          build_system = pp_toml.get("build-system")
      66      else:
      67          build_system = None
      68  
      69      # The following cases must use PEP 517
      70      # We check for use_pep517 being non-None and falsey because that means
      71      # the user explicitly requested --no-use-pep517.  The value 0 as
      72      # opposed to False can occur when the value is provided via an
      73      # environment variable or config file option (due to the quirk of
      74      # strtobool() returning an integer in pip's configuration code).
      75      if has_pyproject and not has_setup:
      76          if use_pep517 is not None and not use_pep517:
      77              raise InstallationError(
      78                  "Disabling PEP 517 processing is invalid: "
      79                  "project does not have a setup.py"
      80              )
      81          use_pep517 = True
      82      elif build_system and "build-backend" in build_system:
      83          if use_pep517 is not None and not use_pep517:
      84              raise InstallationError(
      85                  "Disabling PEP 517 processing is invalid: "
      86                  "project specifies a build backend of {} "
      87                  "in pyproject.toml".format(build_system["build-backend"])
      88              )
      89          use_pep517 = True
      90  
      91      # If we haven't worked out whether to use PEP 517 yet,
      92      # and the user hasn't explicitly stated a preference,
      93      # we do so if the project has a pyproject.toml file
      94      # or if we cannot import setuptools or wheels.
      95  
      96      # We fallback to PEP 517 when without setuptools or without the wheel package,
      97      # so setuptools can be installed as a default build backend.
      98      # For more info see:
      99      # https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9
     100      # https://github.com/pypa/pip/issues/8559
     101      elif use_pep517 is None:
     102          use_pep517 = (
     103              has_pyproject
     104              or not importlib.util.find_spec("setuptools")
     105              or not importlib.util.find_spec("wheel")
     106          )
     107  
     108      # At this point, we know whether we're going to use PEP 517.
     109      assert use_pep517 is not None
     110  
     111      # If we're using the legacy code path, there is nothing further
     112      # for us to do here.
     113      if not use_pep517:
     114          return None
     115  
     116      if build_system is None:
     117          # Either the user has a pyproject.toml with no build-system
     118          # section, or the user has no pyproject.toml, but has opted in
     119          # explicitly via --use-pep517.
     120          # In the absence of any explicit backend specification, we
     121          # assume the setuptools backend that most closely emulates the
     122          # traditional direct setup.py execution, and require wheel and
     123          # a version of setuptools that supports that backend.
     124  
     125          build_system = {
     126              "requires": ["setuptools>=40.8.0", "wheel"],
     127              "build-backend": "setuptools.build_meta:__legacy__",
     128          }
     129  
     130      # If we're using PEP 517, we have build system information (either
     131      # from pyproject.toml, or defaulted by the code above).
     132      # Note that at this point, we do not know if the user has actually
     133      # specified a backend, though.
     134      assert build_system is not None
     135  
     136      # Ensure that the build-system section in pyproject.toml conforms
     137      # to PEP 518.
     138  
     139      # Specifying the build-system table but not the requires key is invalid
     140      if "requires" not in build_system:
     141          raise MissingPyProjectBuildRequires(package=req_name)
     142  
     143      # Error out if requires is not a list of strings
     144      requires = build_system["requires"]
     145      if not _is_list_of_str(requires):
     146          raise InvalidPyProjectBuildRequires(
     147              package=req_name,
     148              reason="It is not a list of strings.",
     149          )
     150  
     151      # Each requirement must be valid as per PEP 508
     152      for requirement in requires:
     153          try:
     154              Requirement(requirement)
     155          except InvalidRequirement as error:
     156              raise InvalidPyProjectBuildRequires(
     157                  package=req_name,
     158                  reason=f"It contains an invalid requirement: {requirement!r}",
     159              ) from error
     160  
     161      backend = build_system.get("build-backend")
     162      backend_path = build_system.get("backend-path", [])
     163      check: List[str] = []
     164      if backend is None:
     165          # If the user didn't specify a backend, we assume they want to use
     166          # the setuptools backend. But we can't be sure they have included
     167          # a version of setuptools which supplies the backend. So we
     168          # make a note to check that this requirement is present once
     169          # we have set up the environment.
     170          # This is quite a lot of work to check for a very specific case. But
     171          # the problem is, that case is potentially quite common - projects that
     172          # adopted PEP 518 early for the ability to specify requirements to
     173          # execute setup.py, but never considered needing to mention the build
     174          # tools themselves. The original PEP 518 code had a similar check (but
     175          # implemented in a different way).
     176          backend = "setuptools.build_meta:__legacy__"
     177          check = ["setuptools>=40.8.0"]
     178  
     179      return BuildSystemDetails(requires, backend, check, backend_path)