python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_internal/
build_env.py
       1  """Build Environment used for isolation during sdist building
       2  """
       3  
       4  import logging
       5  import os
       6  import pathlib
       7  import site
       8  import sys
       9  import textwrap
      10  from collections import OrderedDict
      11  from types import TracebackType
      12  from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
      13  
      14  from pip._vendor.certifi import where
      15  from pip._vendor.packaging.requirements import Requirement
      16  from pip._vendor.packaging.version import Version
      17  
      18  from pip import __file__ as pip_location
      19  from pip._internal.cli.spinners import open_spinner
      20  from pip._internal.locations import get_platlib, get_purelib, get_scheme
      21  from pip._internal.metadata import get_default_environment, get_environment
      22  from pip._internal.utils.subprocess import call_subprocess
      23  from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
      24  
      25  if TYPE_CHECKING:
      26      from pip._internal.index.package_finder import PackageFinder
      27  
      28  logger = logging.getLogger(__name__)
      29  
      30  
      31  def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
      32      return (a, b) if a != b else (a,)
      33  
      34  
      35  class ESC[4;38;5;81m_Prefix:
      36      def __init__(self, path: str) -> None:
      37          self.path = path
      38          self.setup = False
      39          scheme = get_scheme("", prefix=path)
      40          self.bin_dir = scheme.scripts
      41          self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
      42  
      43  
      44  def get_runnable_pip() -> str:
      45      """Get a file to pass to a Python executable, to run the currently-running pip.
      46  
      47      This is used to run a pip subprocess, for installing requirements into the build
      48      environment.
      49      """
      50      source = pathlib.Path(pip_location).resolve().parent
      51  
      52      if not source.is_dir():
      53          # This would happen if someone is using pip from inside a zip file. In that
      54          # case, we can use that directly.
      55          return str(source)
      56  
      57      return os.fsdecode(source / "__pip-runner__.py")
      58  
      59  
      60  def _get_system_sitepackages() -> Set[str]:
      61      """Get system site packages
      62  
      63      Usually from site.getsitepackages,
      64      but fallback on `get_purelib()/get_platlib()` if unavailable
      65      (e.g. in a virtualenv created by virtualenv<20)
      66  
      67      Returns normalized set of strings.
      68      """
      69      if hasattr(site, "getsitepackages"):
      70          system_sites = site.getsitepackages()
      71      else:
      72          # virtualenv < 20 overwrites site.py without getsitepackages
      73          # fallback on get_purelib/get_platlib.
      74          # this is known to miss things, but shouldn't in the cases
      75          # where getsitepackages() has been removed (inside a virtualenv)
      76          system_sites = [get_purelib(), get_platlib()]
      77      return {os.path.normcase(path) for path in system_sites}
      78  
      79  
      80  class ESC[4;38;5;81mBuildEnvironment:
      81      """Creates and manages an isolated environment to install build deps"""
      82  
      83      def __init__(self) -> None:
      84          temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
      85  
      86          self._prefixes = OrderedDict(
      87              (name, _Prefix(os.path.join(temp_dir.path, name)))
      88              for name in ("normal", "overlay")
      89          )
      90  
      91          self._bin_dirs: List[str] = []
      92          self._lib_dirs: List[str] = []
      93          for prefix in reversed(list(self._prefixes.values())):
      94              self._bin_dirs.append(prefix.bin_dir)
      95              self._lib_dirs.extend(prefix.lib_dirs)
      96  
      97          # Customize site to:
      98          # - ensure .pth files are honored
      99          # - prevent access to system site packages
     100          system_sites = _get_system_sitepackages()
     101  
     102          self._site_dir = os.path.join(temp_dir.path, "site")
     103          if not os.path.exists(self._site_dir):
     104              os.mkdir(self._site_dir)
     105          with open(
     106              os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
     107          ) as fp:
     108              fp.write(
     109                  textwrap.dedent(
     110                      """
     111                  import os, site, sys
     112  
     113                  # First, drop system-sites related paths.
     114                  original_sys_path = sys.path[:]
     115                  known_paths = set()
     116                  for path in {system_sites!r}:
     117                      site.addsitedir(path, known_paths=known_paths)
     118                  system_paths = set(
     119                      os.path.normcase(path)
     120                      for path in sys.path[len(original_sys_path):]
     121                  )
     122                  original_sys_path = [
     123                      path for path in original_sys_path
     124                      if os.path.normcase(path) not in system_paths
     125                  ]
     126                  sys.path = original_sys_path
     127  
     128                  # Second, add lib directories.
     129                  # ensuring .pth file are processed.
     130                  for path in {lib_dirs!r}:
     131                      assert not path in sys.path
     132                      site.addsitedir(path)
     133                  """
     134                  ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
     135              )
     136  
     137      def __enter__(self) -> None:
     138          self._save_env = {
     139              name: os.environ.get(name, None)
     140              for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
     141          }
     142  
     143          path = self._bin_dirs[:]
     144          old_path = self._save_env["PATH"]
     145          if old_path:
     146              path.extend(old_path.split(os.pathsep))
     147  
     148          pythonpath = [self._site_dir]
     149  
     150          os.environ.update(
     151              {
     152                  "PATH": os.pathsep.join(path),
     153                  "PYTHONNOUSERSITE": "1",
     154                  "PYTHONPATH": os.pathsep.join(pythonpath),
     155              }
     156          )
     157  
     158      def __exit__(
     159          self,
     160          exc_type: Optional[Type[BaseException]],
     161          exc_val: Optional[BaseException],
     162          exc_tb: Optional[TracebackType],
     163      ) -> None:
     164          for varname, old_value in self._save_env.items():
     165              if old_value is None:
     166                  os.environ.pop(varname, None)
     167              else:
     168                  os.environ[varname] = old_value
     169  
     170      def check_requirements(
     171          self, reqs: Iterable[str]
     172      ) -> Tuple[Set[Tuple[str, str]], Set[str]]:
     173          """Return 2 sets:
     174          - conflicting requirements: set of (installed, wanted) reqs tuples
     175          - missing requirements: set of reqs
     176          """
     177          missing = set()
     178          conflicting = set()
     179          if reqs:
     180              env = (
     181                  get_environment(self._lib_dirs)
     182                  if hasattr(self, "_lib_dirs")
     183                  else get_default_environment()
     184              )
     185              for req_str in reqs:
     186                  req = Requirement(req_str)
     187                  # We're explicitly evaluating with an empty extra value, since build
     188                  # environments are not provided any mechanism to select specific extras.
     189                  if req.marker is not None and not req.marker.evaluate({"extra": ""}):
     190                      continue
     191                  dist = env.get_distribution(req.name)
     192                  if not dist:
     193                      missing.add(req_str)
     194                      continue
     195                  if isinstance(dist.version, Version):
     196                      installed_req_str = f"{req.name}=={dist.version}"
     197                  else:
     198                      installed_req_str = f"{req.name}==={dist.version}"
     199                  if not req.specifier.contains(dist.version, prereleases=True):
     200                      conflicting.add((installed_req_str, req_str))
     201                  # FIXME: Consider direct URL?
     202          return conflicting, missing
     203  
     204      def install_requirements(
     205          self,
     206          finder: "PackageFinder",
     207          requirements: Iterable[str],
     208          prefix_as_string: str,
     209          *,
     210          kind: str,
     211      ) -> None:
     212          prefix = self._prefixes[prefix_as_string]
     213          assert not prefix.setup
     214          prefix.setup = True
     215          if not requirements:
     216              return
     217          self._install_requirements(
     218              get_runnable_pip(),
     219              finder,
     220              requirements,
     221              prefix,
     222              kind=kind,
     223          )
     224  
     225      @staticmethod
     226      def _install_requirements(
     227          pip_runnable: str,
     228          finder: "PackageFinder",
     229          requirements: Iterable[str],
     230          prefix: _Prefix,
     231          *,
     232          kind: str,
     233      ) -> None:
     234          args: List[str] = [
     235              sys.executable,
     236              pip_runnable,
     237              "install",
     238              "--ignore-installed",
     239              "--no-user",
     240              "--prefix",
     241              prefix.path,
     242              "--no-warn-script-location",
     243          ]
     244          if logger.getEffectiveLevel() <= logging.DEBUG:
     245              args.append("-v")
     246          for format_control in ("no_binary", "only_binary"):
     247              formats = getattr(finder.format_control, format_control)
     248              args.extend(
     249                  (
     250                      "--" + format_control.replace("_", "-"),
     251                      ",".join(sorted(formats or {":none:"})),
     252                  )
     253              )
     254  
     255          index_urls = finder.index_urls
     256          if index_urls:
     257              args.extend(["-i", index_urls[0]])
     258              for extra_index in index_urls[1:]:
     259                  args.extend(["--extra-index-url", extra_index])
     260          else:
     261              args.append("--no-index")
     262          for link in finder.find_links:
     263              args.extend(["--find-links", link])
     264  
     265          for host in finder.trusted_hosts:
     266              args.extend(["--trusted-host", host])
     267          if finder.allow_all_prereleases:
     268              args.append("--pre")
     269          if finder.prefer_binary:
     270              args.append("--prefer-binary")
     271          args.append("--")
     272          args.extend(requirements)
     273          extra_environ = {"_PIP_STANDALONE_CERT": where()}
     274          with open_spinner(f"Installing {kind}") as spinner:
     275              call_subprocess(
     276                  args,
     277                  command_desc=f"pip subprocess to install {kind}",
     278                  spinner=spinner,
     279                  extra_environ=extra_environ,
     280              )
     281  
     282  
     283  class ESC[4;38;5;81mNoOpBuildEnvironment(ESC[4;38;5;149mBuildEnvironment):
     284      """A no-op drop-in replacement for BuildEnvironment"""
     285  
     286      def __init__(self) -> None:
     287          pass
     288  
     289      def __enter__(self) -> None:
     290          pass
     291  
     292      def __exit__(
     293          self,
     294          exc_type: Optional[Type[BaseException]],
     295          exc_val: Optional[BaseException],
     296          exc_tb: Optional[TracebackType],
     297      ) -> None:
     298          pass
     299  
     300      def cleanup(self) -> None:
     301          pass
     302  
     303      def install_requirements(
     304          self,
     305          finder: "PackageFinder",
     306          requirements: Iterable[str],
     307          prefix_as_string: str,
     308          *,
     309          kind: str,
     310      ) -> None:
     311          raise NotImplementedError()