Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/pyproject.py
4799 views
import importlib.util1import os2from collections import namedtuple3from typing import Any, List, Optional45from pip._vendor import tomli6from pip._vendor.packaging.requirements import InvalidRequirement, Requirement78from pip._internal.exceptions import (9InstallationError,10InvalidPyProjectBuildRequires,11MissingPyProjectBuildRequires,12)131415def _is_list_of_str(obj: Any) -> bool:16return isinstance(obj, list) and all(isinstance(item, str) for item in obj)171819def make_pyproject_path(unpacked_source_directory: str) -> str:20return os.path.join(unpacked_source_directory, "pyproject.toml")212223BuildSystemDetails = namedtuple(24"BuildSystemDetails", ["requires", "backend", "check", "backend_path"]25)262728def load_pyproject_toml(29use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str30) -> Optional[BuildSystemDetails]:31"""Load the pyproject.toml file.3233Parameters:34use_pep517 - Has the user requested PEP 517 processing? None35means the user hasn't explicitly specified.36pyproject_toml - Location of the project's pyproject.toml file37setup_py - Location of the project's setup.py file38req_name - The name of the requirement we're processing (for39error reporting)4041Returns:42None if we should use the legacy code path, otherwise a tuple43(44requirements from pyproject.toml,45name of PEP 517 backend,46requirements we should check are installed after setting47up the build environment48directory paths to import the backend from (backend-path),49relative to the project root.50)51"""52has_pyproject = os.path.isfile(pyproject_toml)53has_setup = os.path.isfile(setup_py)5455if not has_pyproject and not has_setup:56raise InstallationError(57f"{req_name} does not appear to be a Python project: "58f"neither 'setup.py' nor 'pyproject.toml' found."59)6061if has_pyproject:62with open(pyproject_toml, encoding="utf-8") as f:63pp_toml = tomli.loads(f.read())64build_system = pp_toml.get("build-system")65else:66build_system = None6768# The following cases must use PEP 51769# We check for use_pep517 being non-None and falsey because that means70# the user explicitly requested --no-use-pep517. The value 0 as71# opposed to False can occur when the value is provided via an72# environment variable or config file option (due to the quirk of73# strtobool() returning an integer in pip's configuration code).74if has_pyproject and not has_setup:75if use_pep517 is not None and not use_pep517:76raise InstallationError(77"Disabling PEP 517 processing is invalid: "78"project does not have a setup.py"79)80use_pep517 = True81elif build_system and "build-backend" in build_system:82if use_pep517 is not None and not use_pep517:83raise InstallationError(84"Disabling PEP 517 processing is invalid: "85"project specifies a build backend of {} "86"in pyproject.toml".format(build_system["build-backend"])87)88use_pep517 = True8990# If we haven't worked out whether to use PEP 517 yet,91# and the user hasn't explicitly stated a preference,92# we do so if the project has a pyproject.toml file93# or if we cannot import setuptools.9495# We fallback to PEP 517 when without setuptools,96# so setuptools can be installed as a default build backend.97# For more info see:98# https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/999elif use_pep517 is None:100use_pep517 = has_pyproject or not importlib.util.find_spec("setuptools")101102# At this point, we know whether we're going to use PEP 517.103assert use_pep517 is not None104105# If we're using the legacy code path, there is nothing further106# for us to do here.107if not use_pep517:108return None109110if build_system is None:111# Either the user has a pyproject.toml with no build-system112# section, or the user has no pyproject.toml, but has opted in113# explicitly via --use-pep517.114# In the absence of any explicit backend specification, we115# assume the setuptools backend that most closely emulates the116# traditional direct setup.py execution, and require wheel and117# a version of setuptools that supports that backend.118119build_system = {120"requires": ["setuptools>=40.8.0", "wheel"],121"build-backend": "setuptools.build_meta:__legacy__",122}123124# If we're using PEP 517, we have build system information (either125# from pyproject.toml, or defaulted by the code above).126# Note that at this point, we do not know if the user has actually127# specified a backend, though.128assert build_system is not None129130# Ensure that the build-system section in pyproject.toml conforms131# to PEP 518.132133# Specifying the build-system table but not the requires key is invalid134if "requires" not in build_system:135raise MissingPyProjectBuildRequires(package=req_name)136137# Error out if requires is not a list of strings138requires = build_system["requires"]139if not _is_list_of_str(requires):140raise InvalidPyProjectBuildRequires(141package=req_name,142reason="It is not a list of strings.",143)144145# Each requirement must be valid as per PEP 508146for requirement in requires:147try:148Requirement(requirement)149except InvalidRequirement as error:150raise InvalidPyProjectBuildRequires(151package=req_name,152reason=f"It contains an invalid requirement: {requirement!r}",153) from error154155backend = build_system.get("build-backend")156backend_path = build_system.get("backend-path", [])157check: List[str] = []158if backend is None:159# If the user didn't specify a backend, we assume they want to use160# the setuptools backend. But we can't be sure they have included161# a version of setuptools which supplies the backend, or wheel162# (which is needed by the backend) in their requirements. So we163# make a note to check that those requirements are present once164# we have set up the environment.165# This is quite a lot of work to check for a very specific case. But166# the problem is, that case is potentially quite common - projects that167# adopted PEP 518 early for the ability to specify requirements to168# execute setup.py, but never considered needing to mention the build169# tools themselves. The original PEP 518 code had a similar check (but170# implemented in a different way).171backend = "setuptools.build_meta:__legacy__"172check = ["setuptools>=40.8.0", "wheel"]173174return BuildSystemDetails(requires, backend, check, backend_path)175176177