python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
setuptools/
command/
easy_install.py
       1  """
       2  Easy Install
       3  ------------
       4  
       5  A tool for doing automatic download/extract/build of distutils-based Python
       6  packages.  For detailed documentation, see the accompanying EasyInstall.txt
       7  file, or visit the `EasyInstall home page`__.
       8  
       9  __ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
      10  
      11  """
      12  
      13  from glob import glob
      14  from distutils.util import get_platform
      15  from distutils.util import convert_path, subst_vars
      16  from distutils.errors import (
      17      DistutilsArgError, DistutilsOptionError,
      18      DistutilsError, DistutilsPlatformError,
      19  )
      20  from distutils import log, dir_util
      21  from distutils.command.build_scripts import first_line_re
      22  from distutils.spawn import find_executable
      23  from distutils.command import install
      24  import sys
      25  import os
      26  import zipimport
      27  import shutil
      28  import tempfile
      29  import zipfile
      30  import re
      31  import stat
      32  import random
      33  import textwrap
      34  import warnings
      35  import site
      36  import struct
      37  import contextlib
      38  import subprocess
      39  import shlex
      40  import io
      41  import configparser
      42  import sysconfig
      43  
      44  
      45  from sysconfig import get_path
      46  
      47  from setuptools import SetuptoolsDeprecationWarning
      48  
      49  from setuptools import Command
      50  from setuptools.sandbox import run_setup
      51  from setuptools.command import setopt
      52  from setuptools.archive_util import unpack_archive
      53  from setuptools.package_index import (
      54      PackageIndex, parse_requirement_arg, URL_SCHEME,
      55  )
      56  from setuptools.command import bdist_egg, egg_info
      57  from setuptools.wheel import Wheel
      58  from pkg_resources import (
      59      normalize_path, resource_string,
      60      get_distribution, find_distributions, Environment, Requirement,
      61      Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound,
      62      VersionConflict, DEVELOP_DIST,
      63  )
      64  import pkg_resources
      65  from .._path import ensure_directory
      66  from ..extern.jaraco.text import yield_lines
      67  
      68  
      69  # Turn on PEP440Warnings
      70  warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
      71  
      72  __all__ = [
      73      'easy_install', 'PthDistributions', 'extract_wininst_cfg',
      74      'get_exe_prefixes',
      75  ]
      76  
      77  
      78  def is_64bit():
      79      return struct.calcsize("P") == 8
      80  
      81  
      82  def _to_bytes(s):
      83      return s.encode('utf8')
      84  
      85  
      86  def isascii(s):
      87      try:
      88          s.encode('ascii')
      89          return True
      90      except UnicodeError:
      91          return False
      92  
      93  
      94  def _one_liner(text):
      95      return textwrap.dedent(text).strip().replace('\n', '; ')
      96  
      97  
      98  class ESC[4;38;5;81measy_install(ESC[4;38;5;149mCommand):
      99      """Manage a download/build/install process"""
     100      description = "Find/get/install Python packages"
     101      command_consumes_arguments = True
     102  
     103      user_options = [
     104          ('prefix=', None, "installation prefix"),
     105          ("zip-ok", "z", "install package as a zipfile"),
     106          ("multi-version", "m", "make apps have to require() a version"),
     107          ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"),
     108          ("install-dir=", "d", "install package to DIR"),
     109          ("script-dir=", "s", "install scripts to DIR"),
     110          ("exclude-scripts", "x", "Don't install scripts"),
     111          ("always-copy", "a", "Copy all needed packages to install dir"),
     112          ("index-url=", "i", "base URL of Python Package Index"),
     113          ("find-links=", "f", "additional URL(s) to search for packages"),
     114          ("build-directory=", "b",
     115           "download/extract/build in DIR; keep the results"),
     116          ('optimize=', 'O',
     117           "also compile with optimization: -O1 for \"python -O\", "
     118           "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
     119          ('record=', None,
     120           "filename in which to record list of installed files"),
     121          ('always-unzip', 'Z', "don't install as a zipfile, no matter what"),
     122          ('site-dirs=', 'S', "list of directories where .pth files work"),
     123          ('editable', 'e', "Install specified packages in editable form"),
     124          ('no-deps', 'N', "don't install dependencies"),
     125          ('allow-hosts=', 'H', "pattern(s) that hostnames must match"),
     126          ('local-snapshots-ok', 'l',
     127           "allow building eggs from local checkouts"),
     128          ('version', None, "print version information and exit"),
     129          ('no-find-links', None,
     130           "Don't load find-links defined in packages being installed"),
     131          ('user', None, "install in user site-package '%s'" % site.USER_SITE)
     132      ]
     133      boolean_options = [
     134          'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
     135          'editable',
     136          'no-deps', 'local-snapshots-ok', 'version',
     137          'user'
     138      ]
     139  
     140      negative_opt = {'always-unzip': 'zip-ok'}
     141      create_index = PackageIndex
     142  
     143      def initialize_options(self):
     144          warnings.warn(
     145              "easy_install command is deprecated. "
     146              "Use build and pip and other standards-based tools.",
     147              EasyInstallDeprecationWarning,
     148          )
     149  
     150          # the --user option seems to be an opt-in one,
     151          # so the default should be False.
     152          self.user = 0
     153          self.zip_ok = self.local_snapshots_ok = None
     154          self.install_dir = self.script_dir = self.exclude_scripts = None
     155          self.index_url = None
     156          self.find_links = None
     157          self.build_directory = None
     158          self.args = None
     159          self.optimize = self.record = None
     160          self.upgrade = self.always_copy = self.multi_version = None
     161          self.editable = self.no_deps = self.allow_hosts = None
     162          self.root = self.prefix = self.no_report = None
     163          self.version = None
     164          self.install_purelib = None  # for pure module distributions
     165          self.install_platlib = None  # non-pure (dists w/ extensions)
     166          self.install_headers = None  # for C/C++ headers
     167          self.install_lib = None  # set to either purelib or platlib
     168          self.install_scripts = None
     169          self.install_data = None
     170          self.install_base = None
     171          self.install_platbase = None
     172          self.install_userbase = site.USER_BASE
     173          self.install_usersite = site.USER_SITE
     174          self.no_find_links = None
     175  
     176          # Options not specifiable via command line
     177          self.package_index = None
     178          self.pth_file = self.always_copy_from = None
     179          self.site_dirs = None
     180          self.installed_projects = {}
     181          # Always read easy_install options, even if we are subclassed, or have
     182          # an independent instance created.  This ensures that defaults will
     183          # always come from the standard configuration file(s)' "easy_install"
     184          # section, even if this is a "develop" or "install" command, or some
     185          # other embedding.
     186          self._dry_run = None
     187          self.verbose = self.distribution.verbose
     188          self.distribution._set_command_options(
     189              self, self.distribution.get_option_dict('easy_install')
     190          )
     191  
     192      def delete_blockers(self, blockers):
     193          extant_blockers = (
     194              filename for filename in blockers
     195              if os.path.exists(filename) or os.path.islink(filename)
     196          )
     197          list(map(self._delete_path, extant_blockers))
     198  
     199      def _delete_path(self, path):
     200          log.info("Deleting %s", path)
     201          if self.dry_run:
     202              return
     203  
     204          is_tree = os.path.isdir(path) and not os.path.islink(path)
     205          remover = rmtree if is_tree else os.unlink
     206          remover(path)
     207  
     208      @staticmethod
     209      def _render_version():
     210          """
     211          Render the Setuptools version and installation details, then exit.
     212          """
     213          ver = '{}.{}'.format(*sys.version_info)
     214          dist = get_distribution('setuptools')
     215          tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
     216          print(tmpl.format(**locals()))
     217          raise SystemExit()
     218  
     219      def finalize_options(self):  # noqa: C901  # is too complex (25)  # FIXME
     220          self.version and self._render_version()
     221  
     222          py_version = sys.version.split()[0]
     223  
     224          self.config_vars = dict(sysconfig.get_config_vars())
     225  
     226          self.config_vars.update({
     227              'dist_name': self.distribution.get_name(),
     228              'dist_version': self.distribution.get_version(),
     229              'dist_fullname': self.distribution.get_fullname(),
     230              'py_version': py_version,
     231              'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',
     232              'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
     233              'sys_prefix': self.config_vars['prefix'],
     234              'sys_exec_prefix': self.config_vars['exec_prefix'],
     235              # Only python 3.2+ has abiflags
     236              'abiflags': getattr(sys, 'abiflags', ''),
     237              'platlibdir': getattr(sys, 'platlibdir', 'lib'),
     238          })
     239          with contextlib.suppress(AttributeError):
     240              # only for distutils outside stdlib
     241              self.config_vars.update({
     242                  'implementation_lower': install._get_implementation().lower(),
     243                  'implementation': install._get_implementation(),
     244              })
     245  
     246          # pypa/distutils#113 Python 3.9 compat
     247          self.config_vars.setdefault(
     248              'py_version_nodot_plat',
     249              getattr(sys, 'windir', '').replace('.', ''),
     250          )
     251  
     252          self.config_vars['userbase'] = self.install_userbase
     253          self.config_vars['usersite'] = self.install_usersite
     254          if self.user and not site.ENABLE_USER_SITE:
     255              log.warn("WARNING: The user site-packages directory is disabled.")
     256  
     257          self._fix_install_dir_for_user_site()
     258  
     259          self.expand_basedirs()
     260          self.expand_dirs()
     261  
     262          self._expand(
     263              'install_dir', 'script_dir', 'build_directory',
     264              'site_dirs',
     265          )
     266          # If a non-default installation directory was specified, default the
     267          # script directory to match it.
     268          if self.script_dir is None:
     269              self.script_dir = self.install_dir
     270  
     271          if self.no_find_links is None:
     272              self.no_find_links = False
     273  
     274          # Let install_dir get set by install_lib command, which in turn
     275          # gets its info from the install command, and takes into account
     276          # --prefix and --home and all that other crud.
     277          self.set_undefined_options(
     278              'install_lib', ('install_dir', 'install_dir')
     279          )
     280          # Likewise, set default script_dir from 'install_scripts.install_dir'
     281          self.set_undefined_options(
     282              'install_scripts', ('install_dir', 'script_dir')
     283          )
     284  
     285          if self.user and self.install_purelib:
     286              self.install_dir = self.install_purelib
     287              self.script_dir = self.install_scripts
     288          # default --record from the install command
     289          self.set_undefined_options('install', ('record', 'record'))
     290          self.all_site_dirs = get_site_dirs()
     291          self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))
     292  
     293          if not self.editable:
     294              self.check_site_dir()
     295          default_index = os.getenv("__EASYINSTALL_INDEX", "https://pypi.org/simple/")
     296          # ^ Private API for testing purposes only
     297          self.index_url = self.index_url or default_index
     298          self.shadow_path = self.all_site_dirs[:]
     299          for path_item in self.install_dir, normalize_path(self.script_dir):
     300              if path_item not in self.shadow_path:
     301                  self.shadow_path.insert(0, path_item)
     302  
     303          if self.allow_hosts is not None:
     304              hosts = [s.strip() for s in self.allow_hosts.split(',')]
     305          else:
     306              hosts = ['*']
     307          if self.package_index is None:
     308              self.package_index = self.create_index(
     309                  self.index_url, search_path=self.shadow_path, hosts=hosts,
     310              )
     311          self.local_index = Environment(self.shadow_path + sys.path)
     312  
     313          if self.find_links is not None:
     314              if isinstance(self.find_links, str):
     315                  self.find_links = self.find_links.split()
     316          else:
     317              self.find_links = []
     318          if self.local_snapshots_ok:
     319              self.package_index.scan_egg_links(self.shadow_path + sys.path)
     320          if not self.no_find_links:
     321              self.package_index.add_find_links(self.find_links)
     322          self.set_undefined_options('install_lib', ('optimize', 'optimize'))
     323          self.optimize = self._validate_optimize(self.optimize)
     324  
     325          if self.editable and not self.build_directory:
     326              raise DistutilsArgError(
     327                  "Must specify a build directory (-b) when using --editable"
     328              )
     329          if not self.args:
     330              raise DistutilsArgError(
     331                  "No urls, filenames, or requirements specified (see --help)")
     332  
     333          self.outputs = []
     334  
     335      @staticmethod
     336      def _process_site_dirs(site_dirs):
     337          if site_dirs is None:
     338              return
     339  
     340          normpath = map(normalize_path, sys.path)
     341          site_dirs = [
     342              os.path.expanduser(s.strip()) for s in
     343              site_dirs.split(',')
     344          ]
     345          for d in site_dirs:
     346              if not os.path.isdir(d):
     347                  log.warn("%s (in --site-dirs) does not exist", d)
     348              elif normalize_path(d) not in normpath:
     349                  raise DistutilsOptionError(
     350                      d + " (in --site-dirs) is not on sys.path"
     351                  )
     352              else:
     353                  yield normalize_path(d)
     354  
     355      @staticmethod
     356      def _validate_optimize(value):
     357          try:
     358              value = int(value)
     359              if value not in range(3):
     360                  raise ValueError
     361          except ValueError as e:
     362              raise DistutilsOptionError(
     363                  "--optimize must be 0, 1, or 2"
     364              ) from e
     365  
     366          return value
     367  
     368      def _fix_install_dir_for_user_site(self):
     369          """
     370          Fix the install_dir if "--user" was used.
     371          """
     372          if not self.user:
     373              return
     374  
     375          self.create_home_path()
     376          if self.install_userbase is None:
     377              msg = "User base directory is not specified"
     378              raise DistutilsPlatformError(msg)
     379          self.install_base = self.install_platbase = self.install_userbase
     380          scheme_name = f'{os.name}_user'
     381          self.select_scheme(scheme_name)
     382  
     383      def _expand_attrs(self, attrs):
     384          for attr in attrs:
     385              val = getattr(self, attr)
     386              if val is not None:
     387                  if os.name == 'posix' or os.name == 'nt':
     388                      val = os.path.expanduser(val)
     389                  val = subst_vars(val, self.config_vars)
     390                  setattr(self, attr, val)
     391  
     392      def expand_basedirs(self):
     393          """Calls `os.path.expanduser` on install_base, install_platbase and
     394          root."""
     395          self._expand_attrs(['install_base', 'install_platbase', 'root'])
     396  
     397      def expand_dirs(self):
     398          """Calls `os.path.expanduser` on install dirs."""
     399          dirs = [
     400              'install_purelib',
     401              'install_platlib',
     402              'install_lib',
     403              'install_headers',
     404              'install_scripts',
     405              'install_data',
     406          ]
     407          self._expand_attrs(dirs)
     408  
     409      def run(self, show_deprecation=True):
     410          if show_deprecation:
     411              self.announce(
     412                  "WARNING: The easy_install command is deprecated "
     413                  "and will be removed in a future version.",
     414                  log.WARN,
     415              )
     416          if self.verbose != self.distribution.verbose:
     417              log.set_verbosity(self.verbose)
     418          try:
     419              for spec in self.args:
     420                  self.easy_install(spec, not self.no_deps)
     421              if self.record:
     422                  outputs = self.outputs
     423                  if self.root:  # strip any package prefix
     424                      root_len = len(self.root)
     425                      for counter in range(len(outputs)):
     426                          outputs[counter] = outputs[counter][root_len:]
     427                  from distutils import file_util
     428  
     429                  self.execute(
     430                      file_util.write_file, (self.record, outputs),
     431                      "writing list of installed files to '%s'" %
     432                      self.record
     433                  )
     434              self.warn_deprecated_options()
     435          finally:
     436              log.set_verbosity(self.distribution.verbose)
     437  
     438      def pseudo_tempname(self):
     439          """Return a pseudo-tempname base in the install directory.
     440          This code is intentionally naive; if a malicious party can write to
     441          the target directory you're already in deep doodoo.
     442          """
     443          try:
     444              pid = os.getpid()
     445          except Exception:
     446              pid = random.randint(0, sys.maxsize)
     447          return os.path.join(self.install_dir, "test-easy-install-%s" % pid)
     448  
     449      def warn_deprecated_options(self):
     450          pass
     451  
     452      def check_site_dir(self):  # noqa: C901  # is too complex (12)  # FIXME
     453          """Verify that self.install_dir is .pth-capable dir, if needed"""
     454  
     455          instdir = normalize_path(self.install_dir)
     456          pth_file = os.path.join(instdir, 'easy-install.pth')
     457  
     458          if not os.path.exists(instdir):
     459              try:
     460                  os.makedirs(instdir)
     461              except (OSError, IOError):
     462                  self.cant_write_to_target()
     463  
     464          # Is it a configured, PYTHONPATH, implicit, or explicit site dir?
     465          is_site_dir = instdir in self.all_site_dirs
     466  
     467          if not is_site_dir and not self.multi_version:
     468              # No?  Then directly test whether it does .pth file processing
     469              is_site_dir = self.check_pth_processing()
     470          else:
     471              # make sure we can write to target dir
     472              testfile = self.pseudo_tempname() + '.write-test'
     473              test_exists = os.path.exists(testfile)
     474              try:
     475                  if test_exists:
     476                      os.unlink(testfile)
     477                  open(testfile, 'w').close()
     478                  os.unlink(testfile)
     479              except (OSError, IOError):
     480                  self.cant_write_to_target()
     481  
     482          if not is_site_dir and not self.multi_version:
     483              # Can't install non-multi to non-site dir with easy_install
     484              pythonpath = os.environ.get('PYTHONPATH', '')
     485              log.warn(self.__no_default_msg, self.install_dir, pythonpath)
     486  
     487          if is_site_dir:
     488              if self.pth_file is None:
     489                  self.pth_file = PthDistributions(pth_file, self.all_site_dirs)
     490          else:
     491              self.pth_file = None
     492  
     493          if self.multi_version and not os.path.exists(pth_file):
     494              self.pth_file = None  # don't create a .pth file
     495          self.install_dir = instdir
     496  
     497      __cant_write_msg = textwrap.dedent("""
     498          can't create or remove files in install directory
     499  
     500          The following error occurred while trying to add or remove files in the
     501          installation directory:
     502  
     503              %s
     504  
     505          The installation directory you specified (via --install-dir, --prefix, or
     506          the distutils default setting) was:
     507  
     508              %s
     509          """).lstrip()  # noqa
     510  
     511      __not_exists_id = textwrap.dedent("""
     512          This directory does not currently exist.  Please create it and try again, or
     513          choose a different installation directory (using the -d or --install-dir
     514          option).
     515          """).lstrip()  # noqa
     516  
     517      __access_msg = textwrap.dedent("""
     518          Perhaps your account does not have write access to this directory?  If the
     519          installation directory is a system-owned directory, you may need to sign in
     520          as the administrator or "root" account.  If you do not have administrative
     521          access to this machine, you may wish to choose a different installation
     522          directory, preferably one that is listed in your PYTHONPATH environment
     523          variable.
     524  
     525          For information on other options, you may wish to consult the
     526          documentation at:
     527  
     528            https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
     529  
     530          Please make the appropriate changes for your system and try again.
     531          """).lstrip()  # noqa
     532  
     533      def cant_write_to_target(self):
     534          msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,)
     535  
     536          if not os.path.exists(self.install_dir):
     537              msg += '\n' + self.__not_exists_id
     538          else:
     539              msg += '\n' + self.__access_msg
     540          raise DistutilsError(msg)
     541  
     542      def check_pth_processing(self):
     543          """Empirically verify whether .pth files are supported in inst. dir"""
     544          instdir = self.install_dir
     545          log.info("Checking .pth file support in %s", instdir)
     546          pth_file = self.pseudo_tempname() + ".pth"
     547          ok_file = pth_file + '.ok'
     548          ok_exists = os.path.exists(ok_file)
     549          tmpl = _one_liner("""
     550              import os
     551              f = open({ok_file!r}, 'w')
     552              f.write('OK')
     553              f.close()
     554              """) + '\n'
     555          try:
     556              if ok_exists:
     557                  os.unlink(ok_file)
     558              dirname = os.path.dirname(ok_file)
     559              os.makedirs(dirname, exist_ok=True)
     560              f = open(pth_file, 'w')
     561          except (OSError, IOError):
     562              self.cant_write_to_target()
     563          else:
     564              try:
     565                  f.write(tmpl.format(**locals()))
     566                  f.close()
     567                  f = None
     568                  executable = sys.executable
     569                  if os.name == 'nt':
     570                      dirname, basename = os.path.split(executable)
     571                      alt = os.path.join(dirname, 'pythonw.exe')
     572                      use_alt = (
     573                          basename.lower() == 'python.exe' and
     574                          os.path.exists(alt)
     575                      )
     576                      if use_alt:
     577                          # use pythonw.exe to avoid opening a console window
     578                          executable = alt
     579  
     580                  from distutils.spawn import spawn
     581  
     582                  spawn([executable, '-E', '-c', 'pass'], 0)
     583  
     584                  if os.path.exists(ok_file):
     585                      log.info(
     586                          "TEST PASSED: %s appears to support .pth files",
     587                          instdir
     588                      )
     589                      return True
     590              finally:
     591                  if f:
     592                      f.close()
     593                  if os.path.exists(ok_file):
     594                      os.unlink(ok_file)
     595                  if os.path.exists(pth_file):
     596                      os.unlink(pth_file)
     597          if not self.multi_version:
     598              log.warn("TEST FAILED: %s does NOT support .pth files", instdir)
     599          return False
     600  
     601      def install_egg_scripts(self, dist):
     602          """Write all the scripts for `dist`, unless scripts are excluded"""
     603          if not self.exclude_scripts and dist.metadata_isdir('scripts'):
     604              for script_name in dist.metadata_listdir('scripts'):
     605                  if dist.metadata_isdir('scripts/' + script_name):
     606                      # The "script" is a directory, likely a Python 3
     607                      # __pycache__ directory, so skip it.
     608                      continue
     609                  self.install_script(
     610                      dist, script_name,
     611                      dist.get_metadata('scripts/' + script_name)
     612                  )
     613          self.install_wrapper_scripts(dist)
     614  
     615      def add_output(self, path):
     616          if os.path.isdir(path):
     617              for base, dirs, files in os.walk(path):
     618                  for filename in files:
     619                      self.outputs.append(os.path.join(base, filename))
     620          else:
     621              self.outputs.append(path)
     622  
     623      def not_editable(self, spec):
     624          if self.editable:
     625              raise DistutilsArgError(
     626                  "Invalid argument %r: you can't use filenames or URLs "
     627                  "with --editable (except via the --find-links option)."
     628                  % (spec,)
     629              )
     630  
     631      def check_editable(self, spec):
     632          if not self.editable:
     633              return
     634  
     635          if os.path.exists(os.path.join(self.build_directory, spec.key)):
     636              raise DistutilsArgError(
     637                  "%r already exists in %s; can't do a checkout there" %
     638                  (spec.key, self.build_directory)
     639              )
     640  
     641      @contextlib.contextmanager
     642      def _tmpdir(self):
     643          tmpdir = tempfile.mkdtemp(prefix=u"easy_install-")
     644          try:
     645              # cast to str as workaround for #709 and #710 and #712
     646              yield str(tmpdir)
     647          finally:
     648              os.path.exists(tmpdir) and rmtree(tmpdir)
     649  
     650      def easy_install(self, spec, deps=False):
     651          with self._tmpdir() as tmpdir:
     652              if not isinstance(spec, Requirement):
     653                  if URL_SCHEME(spec):
     654                      # It's a url, download it to tmpdir and process
     655                      self.not_editable(spec)
     656                      dl = self.package_index.download(spec, tmpdir)
     657                      return self.install_item(None, dl, tmpdir, deps, True)
     658  
     659                  elif os.path.exists(spec):
     660                      # Existing file or directory, just process it directly
     661                      self.not_editable(spec)
     662                      return self.install_item(None, spec, tmpdir, deps, True)
     663                  else:
     664                      spec = parse_requirement_arg(spec)
     665  
     666              self.check_editable(spec)
     667              dist = self.package_index.fetch_distribution(
     668                  spec, tmpdir, self.upgrade, self.editable,
     669                  not self.always_copy, self.local_index
     670              )
     671              if dist is None:
     672                  msg = "Could not find suitable distribution for %r" % spec
     673                  if self.always_copy:
     674                      msg += " (--always-copy skips system and development eggs)"
     675                  raise DistutilsError(msg)
     676              elif dist.precedence == DEVELOP_DIST:
     677                  # .egg-info dists don't need installing, just process deps
     678                  self.process_distribution(spec, dist, deps, "Using")
     679                  return dist
     680              else:
     681                  return self.install_item(spec, dist.location, tmpdir, deps)
     682  
     683      def install_item(self, spec, download, tmpdir, deps, install_needed=False):
     684  
     685          # Installation is also needed if file in tmpdir or is not an egg
     686          install_needed = install_needed or self.always_copy
     687          install_needed = install_needed or os.path.dirname(download) == tmpdir
     688          install_needed = install_needed or not download.endswith('.egg')
     689          install_needed = install_needed or (
     690              self.always_copy_from is not None and
     691              os.path.dirname(normalize_path(download)) ==
     692              normalize_path(self.always_copy_from)
     693          )
     694  
     695          if spec and not install_needed:
     696              # at this point, we know it's a local .egg, we just don't know if
     697              # it's already installed.
     698              for dist in self.local_index[spec.project_name]:
     699                  if dist.location == download:
     700                      break
     701              else:
     702                  install_needed = True  # it's not in the local index
     703  
     704          log.info("Processing %s", os.path.basename(download))
     705  
     706          if install_needed:
     707              dists = self.install_eggs(spec, download, tmpdir)
     708              for dist in dists:
     709                  self.process_distribution(spec, dist, deps)
     710          else:
     711              dists = [self.egg_distribution(download)]
     712              self.process_distribution(spec, dists[0], deps, "Using")
     713  
     714          if spec is not None:
     715              for dist in dists:
     716                  if dist in spec:
     717                      return dist
     718  
     719      def select_scheme(self, name):
     720          try:
     721              install._select_scheme(self, name)
     722          except AttributeError:
     723              # stdlib distutils
     724              install.install.select_scheme(self, name.replace('posix', 'unix'))
     725  
     726      # FIXME: 'easy_install.process_distribution' is too complex (12)
     727      def process_distribution(  # noqa: C901
     728              self, requirement, dist, deps=True, *info,
     729      ):
     730          self.update_pth(dist)
     731          self.package_index.add(dist)
     732          if dist in self.local_index[dist.key]:
     733              self.local_index.remove(dist)
     734          self.local_index.add(dist)
     735          self.install_egg_scripts(dist)
     736          self.installed_projects[dist.key] = dist
     737          log.info(self.installation_report(requirement, dist, *info))
     738          if (dist.has_metadata('dependency_links.txt') and
     739                  not self.no_find_links):
     740              self.package_index.add_find_links(
     741                  dist.get_metadata_lines('dependency_links.txt')
     742              )
     743          if not deps and not self.always_copy:
     744              return
     745          elif requirement is not None and dist.key != requirement.key:
     746              log.warn("Skipping dependencies for %s", dist)
     747              return  # XXX this is not the distribution we were looking for
     748          elif requirement is None or dist not in requirement:
     749              # if we wound up with a different version, resolve what we've got
     750              distreq = dist.as_requirement()
     751              requirement = Requirement(str(distreq))
     752          log.info("Processing dependencies for %s", requirement)
     753          try:
     754              distros = WorkingSet([]).resolve(
     755                  [requirement], self.local_index, self.easy_install
     756              )
     757          except DistributionNotFound as e:
     758              raise DistutilsError(str(e)) from e
     759          except VersionConflict as e:
     760              raise DistutilsError(e.report()) from e
     761          if self.always_copy or self.always_copy_from:
     762              # Force all the relevant distros to be copied or activated
     763              for dist in distros:
     764                  if dist.key not in self.installed_projects:
     765                      self.easy_install(dist.as_requirement())
     766          log.info("Finished processing dependencies for %s", requirement)
     767  
     768      def should_unzip(self, dist):
     769          if self.zip_ok is not None:
     770              return not self.zip_ok
     771          if dist.has_metadata('not-zip-safe'):
     772              return True
     773          if not dist.has_metadata('zip-safe'):
     774              return True
     775          return False
     776  
     777      def maybe_move(self, spec, dist_filename, setup_base):
     778          dst = os.path.join(self.build_directory, spec.key)
     779          if os.path.exists(dst):
     780              msg = (
     781                  "%r already exists in %s; build directory %s will not be kept"
     782              )
     783              log.warn(msg, spec.key, self.build_directory, setup_base)
     784              return setup_base
     785          if os.path.isdir(dist_filename):
     786              setup_base = dist_filename
     787          else:
     788              if os.path.dirname(dist_filename) == setup_base:
     789                  os.unlink(dist_filename)  # get it out of the tmp dir
     790              contents = os.listdir(setup_base)
     791              if len(contents) == 1:
     792                  dist_filename = os.path.join(setup_base, contents[0])
     793                  if os.path.isdir(dist_filename):
     794                      # if the only thing there is a directory, move it instead
     795                      setup_base = dist_filename
     796          ensure_directory(dst)
     797          shutil.move(setup_base, dst)
     798          return dst
     799  
     800      def install_wrapper_scripts(self, dist):
     801          if self.exclude_scripts:
     802              return
     803          for args in ScriptWriter.best().get_args(dist):
     804              self.write_script(*args)
     805  
     806      def install_script(self, dist, script_name, script_text, dev_path=None):
     807          """Generate a legacy script wrapper and install it"""
     808          spec = str(dist.as_requirement())
     809          is_script = is_python_script(script_text, script_name)
     810  
     811          if is_script:
     812              body = self._load_template(dev_path) % locals()
     813              script_text = ScriptWriter.get_header(script_text) + body
     814          self.write_script(script_name, _to_bytes(script_text), 'b')
     815  
     816      @staticmethod
     817      def _load_template(dev_path):
     818          """
     819          There are a couple of template scripts in the package. This
     820          function loads one of them and prepares it for use.
     821          """
     822          # See https://github.com/pypa/setuptools/issues/134 for info
     823          # on script file naming and downstream issues with SVR4
     824          name = 'script.tmpl'
     825          if dev_path:
     826              name = name.replace('.tmpl', ' (dev).tmpl')
     827  
     828          raw_bytes = resource_string('setuptools', name)
     829          return raw_bytes.decode('utf-8')
     830  
     831      def write_script(self, script_name, contents, mode="t", blockers=()):
     832          """Write an executable file to the scripts directory"""
     833          self.delete_blockers(  # clean up old .py/.pyw w/o a script
     834              [os.path.join(self.script_dir, x) for x in blockers]
     835          )
     836          log.info("Installing %s script to %s", script_name, self.script_dir)
     837          target = os.path.join(self.script_dir, script_name)
     838          self.add_output(target)
     839  
     840          if self.dry_run:
     841              return
     842  
     843          mask = current_umask()
     844          ensure_directory(target)
     845          if os.path.exists(target):
     846              os.unlink(target)
     847          with open(target, "w" + mode) as f:
     848              f.write(contents)
     849          chmod(target, 0o777 - mask)
     850  
     851      def install_eggs(self, spec, dist_filename, tmpdir):
     852          # .egg dirs or files are already built, so just return them
     853          installer_map = {
     854              '.egg': self.install_egg,
     855              '.exe': self.install_exe,
     856              '.whl': self.install_wheel,
     857          }
     858          try:
     859              install_dist = installer_map[
     860                  dist_filename.lower()[-4:]
     861              ]
     862          except KeyError:
     863              pass
     864          else:
     865              return [install_dist(dist_filename, tmpdir)]
     866  
     867          # Anything else, try to extract and build
     868          setup_base = tmpdir
     869          if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'):
     870              unpack_archive(dist_filename, tmpdir, self.unpack_progress)
     871          elif os.path.isdir(dist_filename):
     872              setup_base = os.path.abspath(dist_filename)
     873  
     874          if (setup_base.startswith(tmpdir)  # something we downloaded
     875                  and self.build_directory and spec is not None):
     876              setup_base = self.maybe_move(spec, dist_filename, setup_base)
     877  
     878          # Find the setup.py file
     879          setup_script = os.path.join(setup_base, 'setup.py')
     880  
     881          if not os.path.exists(setup_script):
     882              setups = glob(os.path.join(setup_base, '*', 'setup.py'))
     883              if not setups:
     884                  raise DistutilsError(
     885                      "Couldn't find a setup script in %s" %
     886                      os.path.abspath(dist_filename)
     887                  )
     888              if len(setups) > 1:
     889                  raise DistutilsError(
     890                      "Multiple setup scripts in %s" %
     891                      os.path.abspath(dist_filename)
     892                  )
     893              setup_script = setups[0]
     894  
     895          # Now run it, and return the result
     896          if self.editable:
     897              log.info(self.report_editable(spec, setup_script))
     898              return []
     899          else:
     900              return self.build_and_install(setup_script, setup_base)
     901  
     902      def egg_distribution(self, egg_path):
     903          if os.path.isdir(egg_path):
     904              metadata = PathMetadata(egg_path, os.path.join(egg_path,
     905                                                             'EGG-INFO'))
     906          else:
     907              metadata = EggMetadata(zipimport.zipimporter(egg_path))
     908          return Distribution.from_filename(egg_path, metadata=metadata)
     909  
     910      # FIXME: 'easy_install.install_egg' is too complex (11)
     911      def install_egg(self, egg_path, tmpdir):  # noqa: C901
     912          destination = os.path.join(
     913              self.install_dir,
     914              os.path.basename(egg_path),
     915          )
     916          destination = os.path.abspath(destination)
     917          if not self.dry_run:
     918              ensure_directory(destination)
     919  
     920          dist = self.egg_distribution(egg_path)
     921          if not (
     922              os.path.exists(destination) and os.path.samefile(egg_path, destination)
     923          ):
     924              if os.path.isdir(destination) and not os.path.islink(destination):
     925                  dir_util.remove_tree(destination, dry_run=self.dry_run)
     926              elif os.path.exists(destination):
     927                  self.execute(
     928                      os.unlink,
     929                      (destination,),
     930                      "Removing " + destination,
     931                  )
     932              try:
     933                  new_dist_is_zipped = False
     934                  if os.path.isdir(egg_path):
     935                      if egg_path.startswith(tmpdir):
     936                          f, m = shutil.move, "Moving"
     937                      else:
     938                          f, m = shutil.copytree, "Copying"
     939                  elif self.should_unzip(dist):
     940                      self.mkpath(destination)
     941                      f, m = self.unpack_and_compile, "Extracting"
     942                  else:
     943                      new_dist_is_zipped = True
     944                      if egg_path.startswith(tmpdir):
     945                          f, m = shutil.move, "Moving"
     946                      else:
     947                          f, m = shutil.copy2, "Copying"
     948                  self.execute(
     949                      f,
     950                      (egg_path, destination),
     951                      (m + " %s to %s") % (
     952                          os.path.basename(egg_path),
     953                          os.path.dirname(destination)
     954                      ),
     955                  )
     956                  update_dist_caches(
     957                      destination,
     958                      fix_zipimporter_caches=new_dist_is_zipped,
     959                  )
     960              except Exception:
     961                  update_dist_caches(destination, fix_zipimporter_caches=False)
     962                  raise
     963  
     964          self.add_output(destination)
     965          return self.egg_distribution(destination)
     966  
     967      def install_exe(self, dist_filename, tmpdir):
     968          # See if it's valid, get data
     969          cfg = extract_wininst_cfg(dist_filename)
     970          if cfg is None:
     971              raise DistutilsError(
     972                  "%s is not a valid distutils Windows .exe" % dist_filename
     973              )
     974          # Create a dummy distribution object until we build the real distro
     975          dist = Distribution(
     976              None,
     977              project_name=cfg.get('metadata', 'name'),
     978              version=cfg.get('metadata', 'version'), platform=get_platform(),
     979          )
     980  
     981          # Convert the .exe to an unpacked egg
     982          egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg')
     983          dist.location = egg_path
     984          egg_tmp = egg_path + '.tmp'
     985          _egg_info = os.path.join(egg_tmp, 'EGG-INFO')
     986          pkg_inf = os.path.join(_egg_info, 'PKG-INFO')
     987          ensure_directory(pkg_inf)  # make sure EGG-INFO dir exists
     988          dist._provider = PathMetadata(egg_tmp, _egg_info)  # XXX
     989          self.exe_to_egg(dist_filename, egg_tmp)
     990  
     991          # Write EGG-INFO/PKG-INFO
     992          if not os.path.exists(pkg_inf):
     993              f = open(pkg_inf, 'w')
     994              f.write('Metadata-Version: 1.0\n')
     995              for k, v in cfg.items('metadata'):
     996                  if k != 'target_version':
     997                      f.write('%s: %s\n' % (k.replace('_', '-').title(), v))
     998              f.close()
     999          script_dir = os.path.join(_egg_info, 'scripts')
    1000          # delete entry-point scripts to avoid duping
    1001          self.delete_blockers([
    1002              os.path.join(script_dir, args[0])
    1003              for args in ScriptWriter.get_args(dist)
    1004          ])
    1005          # Build .egg file from tmpdir
    1006          bdist_egg.make_zipfile(
    1007              egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run,
    1008          )
    1009          # install the .egg
    1010          return self.install_egg(egg_path, tmpdir)
    1011  
    1012      # FIXME: 'easy_install.exe_to_egg' is too complex (12)
    1013      def exe_to_egg(self, dist_filename, egg_tmp):  # noqa: C901
    1014          """Extract a bdist_wininst to the directories an egg would use"""
    1015          # Check for .pth file and set up prefix translations
    1016          prefixes = get_exe_prefixes(dist_filename)
    1017          to_compile = []
    1018          native_libs = []
    1019          top_level = {}
    1020  
    1021          def process(src, dst):
    1022              s = src.lower()
    1023              for old, new in prefixes:
    1024                  if s.startswith(old):
    1025                      src = new + src[len(old):]
    1026                      parts = src.split('/')
    1027                      dst = os.path.join(egg_tmp, *parts)
    1028                      dl = dst.lower()
    1029                      if dl.endswith('.pyd') or dl.endswith('.dll'):
    1030                          parts[-1] = bdist_egg.strip_module(parts[-1])
    1031                          top_level[os.path.splitext(parts[0])[0]] = 1
    1032                          native_libs.append(src)
    1033                      elif dl.endswith('.py') and old != 'SCRIPTS/':
    1034                          top_level[os.path.splitext(parts[0])[0]] = 1
    1035                          to_compile.append(dst)
    1036                      return dst
    1037              if not src.endswith('.pth'):
    1038                  log.warn("WARNING: can't process %s", src)
    1039              return None
    1040  
    1041          # extract, tracking .pyd/.dll->native_libs and .py -> to_compile
    1042          unpack_archive(dist_filename, egg_tmp, process)
    1043          stubs = []
    1044          for res in native_libs:
    1045              if res.lower().endswith('.pyd'):  # create stubs for .pyd's
    1046                  parts = res.split('/')
    1047                  resource = parts[-1]
    1048                  parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py'
    1049                  pyfile = os.path.join(egg_tmp, *parts)
    1050                  to_compile.append(pyfile)
    1051                  stubs.append(pyfile)
    1052                  bdist_egg.write_stub(resource, pyfile)
    1053          self.byte_compile(to_compile)  # compile .py's
    1054          bdist_egg.write_safety_flag(
    1055              os.path.join(egg_tmp, 'EGG-INFO'),
    1056              bdist_egg.analyze_egg(egg_tmp, stubs))  # write zip-safety flag
    1057  
    1058          for name in 'top_level', 'native_libs':
    1059              if locals()[name]:
    1060                  txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')
    1061                  if not os.path.exists(txt):
    1062                      f = open(txt, 'w')
    1063                      f.write('\n'.join(locals()[name]) + '\n')
    1064                      f.close()
    1065  
    1066      def install_wheel(self, wheel_path, tmpdir):
    1067          wheel = Wheel(wheel_path)
    1068          assert wheel.is_compatible()
    1069          destination = os.path.join(self.install_dir, wheel.egg_name())
    1070          destination = os.path.abspath(destination)
    1071          if not self.dry_run:
    1072              ensure_directory(destination)
    1073          if os.path.isdir(destination) and not os.path.islink(destination):
    1074              dir_util.remove_tree(destination, dry_run=self.dry_run)
    1075          elif os.path.exists(destination):
    1076              self.execute(
    1077                  os.unlink,
    1078                  (destination,),
    1079                  "Removing " + destination,
    1080              )
    1081          try:
    1082              self.execute(
    1083                  wheel.install_as_egg,
    1084                  (destination,),
    1085                  ("Installing %s to %s") % (
    1086                      os.path.basename(wheel_path),
    1087                      os.path.dirname(destination)
    1088                  ),
    1089              )
    1090          finally:
    1091              update_dist_caches(destination, fix_zipimporter_caches=False)
    1092          self.add_output(destination)
    1093          return self.egg_distribution(destination)
    1094  
    1095      __mv_warning = textwrap.dedent("""
    1096          Because this distribution was installed --multi-version, before you can
    1097          import modules from this package in an application, you will need to
    1098          'import pkg_resources' and then use a 'require()' call similar to one of
    1099          these examples, in order to select the desired version:
    1100  
    1101              pkg_resources.require("%(name)s")  # latest installed version
    1102              pkg_resources.require("%(name)s==%(version)s")  # this exact version
    1103              pkg_resources.require("%(name)s>=%(version)s")  # this version or higher
    1104          """).lstrip()  # noqa
    1105  
    1106      __id_warning = textwrap.dedent("""
    1107          Note also that the installation directory must be on sys.path at runtime for
    1108          this to work.  (e.g. by being the application's script directory, by being on
    1109          PYTHONPATH, or by being added to sys.path by your code.)
    1110          """)  # noqa
    1111  
    1112      def installation_report(self, req, dist, what="Installed"):
    1113          """Helpful installation message for display to package users"""
    1114          msg = "\n%(what)s %(eggloc)s%(extras)s"
    1115          if self.multi_version and not self.no_report:
    1116              msg += '\n' + self.__mv_warning
    1117              if self.install_dir not in map(normalize_path, sys.path):
    1118                  msg += '\n' + self.__id_warning
    1119  
    1120          eggloc = dist.location
    1121          name = dist.project_name
    1122          version = dist.version
    1123          extras = ''  # TODO: self.report_extras(req, dist)
    1124          return msg % locals()
    1125  
    1126      __editable_msg = textwrap.dedent("""
    1127          Extracted editable version of %(spec)s to %(dirname)s
    1128  
    1129          If it uses setuptools in its setup script, you can activate it in
    1130          "development" mode by going to that directory and running::
    1131  
    1132              %(python)s setup.py develop
    1133  
    1134          See the setuptools documentation for the "develop" command for more info.
    1135          """).lstrip()  # noqa
    1136  
    1137      def report_editable(self, spec, setup_script):
    1138          dirname = os.path.dirname(setup_script)
    1139          python = sys.executable
    1140          return '\n' + self.__editable_msg % locals()
    1141  
    1142      def run_setup(self, setup_script, setup_base, args):
    1143          sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
    1144          sys.modules.setdefault('distutils.command.egg_info', egg_info)
    1145  
    1146          args = list(args)
    1147          if self.verbose > 2:
    1148              v = 'v' * (self.verbose - 1)
    1149              args.insert(0, '-' + v)
    1150          elif self.verbose < 2:
    1151              args.insert(0, '-q')
    1152          if self.dry_run:
    1153              args.insert(0, '-n')
    1154          log.info(
    1155              "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args)
    1156          )
    1157          try:
    1158              run_setup(setup_script, args)
    1159          except SystemExit as v:
    1160              raise DistutilsError(
    1161                  "Setup script exited with %s" % (v.args[0],)
    1162              ) from v
    1163  
    1164      def build_and_install(self, setup_script, setup_base):
    1165          args = ['bdist_egg', '--dist-dir']
    1166  
    1167          dist_dir = tempfile.mkdtemp(
    1168              prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script)
    1169          )
    1170          try:
    1171              self._set_fetcher_options(os.path.dirname(setup_script))
    1172              args.append(dist_dir)
    1173  
    1174              self.run_setup(setup_script, setup_base, args)
    1175              all_eggs = Environment([dist_dir])
    1176              eggs = []
    1177              for key in all_eggs:
    1178                  for dist in all_eggs[key]:
    1179                      eggs.append(self.install_egg(dist.location, setup_base))
    1180              if not eggs and not self.dry_run:
    1181                  log.warn("No eggs found in %s (setup script problem?)",
    1182                           dist_dir)
    1183              return eggs
    1184          finally:
    1185              rmtree(dist_dir)
    1186              log.set_verbosity(self.verbose)  # restore our log verbosity
    1187  
    1188      def _set_fetcher_options(self, base):
    1189          """
    1190          When easy_install is about to run bdist_egg on a source dist, that
    1191          source dist might have 'setup_requires' directives, requiring
    1192          additional fetching. Ensure the fetcher options given to easy_install
    1193          are available to that command as well.
    1194          """
    1195          # find the fetch options from easy_install and write them out
    1196          # to the setup.cfg file.
    1197          ei_opts = self.distribution.get_option_dict('easy_install').copy()
    1198          fetch_directives = (
    1199              'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',
    1200          )
    1201          fetch_options = {}
    1202          for key, val in ei_opts.items():
    1203              if key not in fetch_directives:
    1204                  continue
    1205              fetch_options[key] = val[1]
    1206          # create a settings dictionary suitable for `edit_config`
    1207          settings = dict(easy_install=fetch_options)
    1208          cfg_filename = os.path.join(base, 'setup.cfg')
    1209          setopt.edit_config(cfg_filename, settings)
    1210  
    1211      def update_pth(self, dist):  # noqa: C901  # is too complex (11)  # FIXME
    1212          if self.pth_file is None:
    1213              return
    1214  
    1215          for d in self.pth_file[dist.key]:  # drop old entries
    1216              if not self.multi_version and d.location == dist.location:
    1217                  continue
    1218  
    1219              log.info("Removing %s from easy-install.pth file", d)
    1220              self.pth_file.remove(d)
    1221              if d.location in self.shadow_path:
    1222                  self.shadow_path.remove(d.location)
    1223  
    1224          if not self.multi_version:
    1225              if dist.location in self.pth_file.paths:
    1226                  log.info(
    1227                      "%s is already the active version in easy-install.pth",
    1228                      dist,
    1229                  )
    1230              else:
    1231                  log.info("Adding %s to easy-install.pth file", dist)
    1232                  self.pth_file.add(dist)  # add new entry
    1233                  if dist.location not in self.shadow_path:
    1234                      self.shadow_path.append(dist.location)
    1235  
    1236          if self.dry_run:
    1237              return
    1238  
    1239          self.pth_file.save()
    1240  
    1241          if dist.key != 'setuptools':
    1242              return
    1243  
    1244          # Ensure that setuptools itself never becomes unavailable!
    1245          # XXX should this check for latest version?
    1246          filename = os.path.join(self.install_dir, 'setuptools.pth')
    1247          if os.path.islink(filename):
    1248              os.unlink(filename)
    1249          with open(filename, 'wt') as f:
    1250              f.write(self.pth_file.make_relative(dist.location) + '\n')
    1251  
    1252      def unpack_progress(self, src, dst):
    1253          # Progress filter for unpacking
    1254          log.debug("Unpacking %s to %s", src, dst)
    1255          return dst  # only unpack-and-compile skips files for dry run
    1256  
    1257      def unpack_and_compile(self, egg_path, destination):
    1258          to_compile = []
    1259          to_chmod = []
    1260  
    1261          def pf(src, dst):
    1262              if dst.endswith('.py') and not src.startswith('EGG-INFO/'):
    1263                  to_compile.append(dst)
    1264              elif dst.endswith('.dll') or dst.endswith('.so'):
    1265                  to_chmod.append(dst)
    1266              self.unpack_progress(src, dst)
    1267              return not self.dry_run and dst or None
    1268  
    1269          unpack_archive(egg_path, destination, pf)
    1270          self.byte_compile(to_compile)
    1271          if not self.dry_run:
    1272              for f in to_chmod:
    1273                  mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755
    1274                  chmod(f, mode)
    1275  
    1276      def byte_compile(self, to_compile):
    1277          if sys.dont_write_bytecode:
    1278              return
    1279  
    1280          from distutils.util import byte_compile
    1281  
    1282          try:
    1283              # try to make the byte compile messages quieter
    1284              log.set_verbosity(self.verbose - 1)
    1285  
    1286              byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)
    1287              if self.optimize:
    1288                  byte_compile(
    1289                      to_compile, optimize=self.optimize, force=1,
    1290                      dry_run=self.dry_run,
    1291                  )
    1292          finally:
    1293              log.set_verbosity(self.verbose)  # restore original verbosity
    1294  
    1295      __no_default_msg = textwrap.dedent("""
    1296          bad install directory or PYTHONPATH
    1297  
    1298          You are attempting to install a package to a directory that is not
    1299          on PYTHONPATH and which Python does not read ".pth" files from.  The
    1300          installation directory you specified (via --install-dir, --prefix, or
    1301          the distutils default setting) was:
    1302  
    1303              %s
    1304  
    1305          and your PYTHONPATH environment variable currently contains:
    1306  
    1307              %r
    1308  
    1309          Here are some of your options for correcting the problem:
    1310  
    1311          * You can choose a different installation directory, i.e., one that is
    1312            on PYTHONPATH or supports .pth files
    1313  
    1314          * You can add the installation directory to the PYTHONPATH environment
    1315            variable.  (It must then also be on PYTHONPATH whenever you run
    1316            Python and want to use the package(s) you are installing.)
    1317  
    1318          * You can set up the installation directory to support ".pth" files by
    1319            using one of the approaches described here:
    1320  
    1321            https://setuptools.pypa.io/en/latest/deprecated/easy_install.html#custom-installation-locations
    1322  
    1323  
    1324          Please make the appropriate changes for your system and try again.
    1325          """).strip()
    1326  
    1327      def create_home_path(self):
    1328          """Create directories under ~."""
    1329          if not self.user:
    1330              return
    1331          home = convert_path(os.path.expanduser("~"))
    1332          for path in only_strs(self.config_vars.values()):
    1333              if path.startswith(home) and not os.path.isdir(path):
    1334                  self.debug_print("os.makedirs('%s', 0o700)" % path)
    1335                  os.makedirs(path, 0o700)
    1336  
    1337      INSTALL_SCHEMES = dict(
    1338          posix=dict(
    1339              install_dir='$base/lib/python$py_version_short/site-packages',
    1340              script_dir='$base/bin',
    1341          ),
    1342      )
    1343  
    1344      DEFAULT_SCHEME = dict(
    1345          install_dir='$base/Lib/site-packages',
    1346          script_dir='$base/Scripts',
    1347      )
    1348  
    1349      def _expand(self, *attrs):
    1350          config_vars = self.get_finalized_command('install').config_vars
    1351  
    1352          if self.prefix:
    1353              # Set default install_dir/scripts from --prefix
    1354              config_vars = dict(config_vars)
    1355              config_vars['base'] = self.prefix
    1356              scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME)
    1357              for attr, val in scheme.items():
    1358                  if getattr(self, attr, None) is None:
    1359                      setattr(self, attr, val)
    1360  
    1361          from distutils.util import subst_vars
    1362  
    1363          for attr in attrs:
    1364              val = getattr(self, attr)
    1365              if val is not None:
    1366                  val = subst_vars(val, config_vars)
    1367                  if os.name == 'posix':
    1368                      val = os.path.expanduser(val)
    1369                  setattr(self, attr, val)
    1370  
    1371  
    1372  def _pythonpath():
    1373      items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
    1374      return filter(None, items)
    1375  
    1376  
    1377  def get_site_dirs():
    1378      """
    1379      Return a list of 'site' dirs
    1380      """
    1381  
    1382      sitedirs = []
    1383  
    1384      # start with PYTHONPATH
    1385      sitedirs.extend(_pythonpath())
    1386  
    1387      prefixes = [sys.prefix]
    1388      if sys.exec_prefix != sys.prefix:
    1389          prefixes.append(sys.exec_prefix)
    1390      for prefix in prefixes:
    1391          if not prefix:
    1392              continue
    1393  
    1394          if sys.platform in ('os2emx', 'riscos'):
    1395              sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
    1396          elif os.sep == '/':
    1397              sitedirs.extend([
    1398                  os.path.join(
    1399                      prefix,
    1400                      "lib",
    1401                      "python{}.{}".format(*sys.version_info),
    1402                      "site-packages",
    1403                  ),
    1404                  os.path.join(prefix, "lib", "site-python"),
    1405              ])
    1406          else:
    1407              sitedirs.extend([
    1408                  prefix,
    1409                  os.path.join(prefix, "lib", "site-packages"),
    1410              ])
    1411          if sys.platform != 'darwin':
    1412              continue
    1413  
    1414          # for framework builds *only* we add the standard Apple
    1415          # locations. Currently only per-user, but /Library and
    1416          # /Network/Library could be added too
    1417          if 'Python.framework' not in prefix:
    1418              continue
    1419  
    1420          home = os.environ.get('HOME')
    1421          if not home:
    1422              continue
    1423  
    1424          home_sp = os.path.join(
    1425              home,
    1426              'Library',
    1427              'Python',
    1428              '{}.{}'.format(*sys.version_info),
    1429              'site-packages',
    1430          )
    1431          sitedirs.append(home_sp)
    1432      lib_paths = get_path('purelib'), get_path('platlib')
    1433  
    1434      sitedirs.extend(s for s in lib_paths if s not in sitedirs)
    1435  
    1436      if site.ENABLE_USER_SITE:
    1437          sitedirs.append(site.USER_SITE)
    1438  
    1439      with contextlib.suppress(AttributeError):
    1440          sitedirs.extend(site.getsitepackages())
    1441  
    1442      sitedirs = list(map(normalize_path, sitedirs))
    1443  
    1444      return sitedirs
    1445  
    1446  
    1447  def expand_paths(inputs):  # noqa: C901  # is too complex (11)  # FIXME
    1448      """Yield sys.path directories that might contain "old-style" packages"""
    1449  
    1450      seen = {}
    1451  
    1452      for dirname in inputs:
    1453          dirname = normalize_path(dirname)
    1454          if dirname in seen:
    1455              continue
    1456  
    1457          seen[dirname] = 1
    1458          if not os.path.isdir(dirname):
    1459              continue
    1460  
    1461          files = os.listdir(dirname)
    1462          yield dirname, files
    1463  
    1464          for name in files:
    1465              if not name.endswith('.pth'):
    1466                  # We only care about the .pth files
    1467                  continue
    1468              if name in ('easy-install.pth', 'setuptools.pth'):
    1469                  # Ignore .pth files that we control
    1470                  continue
    1471  
    1472              # Read the .pth file
    1473              f = open(os.path.join(dirname, name))
    1474              lines = list(yield_lines(f))
    1475              f.close()
    1476  
    1477              # Yield existing non-dupe, non-import directory lines from it
    1478              for line in lines:
    1479                  if line.startswith("import"):
    1480                      continue
    1481  
    1482                  line = normalize_path(line.rstrip())
    1483                  if line in seen:
    1484                      continue
    1485  
    1486                  seen[line] = 1
    1487                  if not os.path.isdir(line):
    1488                      continue
    1489  
    1490                  yield line, os.listdir(line)
    1491  
    1492  
    1493  def extract_wininst_cfg(dist_filename):
    1494      """Extract configuration data from a bdist_wininst .exe
    1495  
    1496      Returns a configparser.RawConfigParser, or None
    1497      """
    1498      f = open(dist_filename, 'rb')
    1499      try:
    1500          endrec = zipfile._EndRecData(f)
    1501          if endrec is None:
    1502              return None
    1503  
    1504          prepended = (endrec[9] - endrec[5]) - endrec[6]
    1505          if prepended < 12:  # no wininst data here
    1506              return None
    1507          f.seek(prepended - 12)
    1508  
    1509          tag, cfglen, bmlen = struct.unpack("<iii", f.read(12))
    1510          if tag not in (0x1234567A, 0x1234567B):
    1511              return None  # not a valid tag
    1512  
    1513          f.seek(prepended - (12 + cfglen))
    1514          init = {'version': '', 'target_version': ''}
    1515          cfg = configparser.RawConfigParser(init)
    1516          try:
    1517              part = f.read(cfglen)
    1518              # Read up to the first null byte.
    1519              config = part.split(b'\0', 1)[0]
    1520              # Now the config is in bytes, but for RawConfigParser, it should
    1521              #  be text, so decode it.
    1522              config = config.decode(sys.getfilesystemencoding())
    1523              cfg.read_file(io.StringIO(config))
    1524          except configparser.Error:
    1525              return None
    1526          if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
    1527              return None
    1528          return cfg
    1529  
    1530      finally:
    1531          f.close()
    1532  
    1533  
    1534  def get_exe_prefixes(exe_filename):
    1535      """Get exe->egg path translations for a given .exe file"""
    1536  
    1537      prefixes = [
    1538          ('PURELIB/', ''),
    1539          ('PLATLIB/pywin32_system32', ''),
    1540          ('PLATLIB/', ''),
    1541          ('SCRIPTS/', 'EGG-INFO/scripts/'),
    1542          ('DATA/lib/site-packages', ''),
    1543      ]
    1544      z = zipfile.ZipFile(exe_filename)
    1545      try:
    1546          for info in z.infolist():
    1547              name = info.filename
    1548              parts = name.split('/')
    1549              if len(parts) == 3 and parts[2] == 'PKG-INFO':
    1550                  if parts[1].endswith('.egg-info'):
    1551                      prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/'))
    1552                      break
    1553              if len(parts) != 2 or not name.endswith('.pth'):
    1554                  continue
    1555              if name.endswith('-nspkg.pth'):
    1556                  continue
    1557              if parts[0].upper() in ('PURELIB', 'PLATLIB'):
    1558                  contents = z.read(name).decode()
    1559                  for pth in yield_lines(contents):
    1560                      pth = pth.strip().replace('\\', '/')
    1561                      if not pth.startswith('import'):
    1562                          prefixes.append((('%s/%s/' % (parts[0], pth)), ''))
    1563      finally:
    1564          z.close()
    1565      prefixes = [(x.lower(), y) for x, y in prefixes]
    1566      prefixes.sort()
    1567      prefixes.reverse()
    1568      return prefixes
    1569  
    1570  
    1571  class ESC[4;38;5;81mPthDistributions(ESC[4;38;5;149mEnvironment):
    1572      """A .pth file with Distribution paths in it"""
    1573  
    1574      dirty = False
    1575  
    1576      def __init__(self, filename, sitedirs=()):
    1577          self.filename = filename
    1578          self.sitedirs = list(map(normalize_path, sitedirs))
    1579          self.basedir = normalize_path(os.path.dirname(self.filename))
    1580          self._load()
    1581          super().__init__([], None, None)
    1582          for path in yield_lines(self.paths):
    1583              list(map(self.add, find_distributions(path, True)))
    1584  
    1585      def _load(self):
    1586          self.paths = []
    1587          saw_import = False
    1588          seen = dict.fromkeys(self.sitedirs)
    1589          if os.path.isfile(self.filename):
    1590              f = open(self.filename, 'rt')
    1591              for line in f:
    1592                  if line.startswith('import'):
    1593                      saw_import = True
    1594                      continue
    1595                  path = line.rstrip()
    1596                  self.paths.append(path)
    1597                  if not path.strip() or path.strip().startswith('#'):
    1598                      continue
    1599                  # skip non-existent paths, in case somebody deleted a package
    1600                  # manually, and duplicate paths as well
    1601                  path = self.paths[-1] = normalize_path(
    1602                      os.path.join(self.basedir, path)
    1603                  )
    1604                  if not os.path.exists(path) or path in seen:
    1605                      self.paths.pop()  # skip it
    1606                      self.dirty = True  # we cleaned up, so we're dirty now :)
    1607                      continue
    1608                  seen[path] = 1
    1609              f.close()
    1610  
    1611          if self.paths and not saw_import:
    1612              self.dirty = True  # ensure anything we touch has import wrappers
    1613          while self.paths and not self.paths[-1].strip():
    1614              self.paths.pop()
    1615  
    1616      def save(self):
    1617          """Write changed .pth file back to disk"""
    1618          if not self.dirty:
    1619              return
    1620  
    1621          rel_paths = list(map(self.make_relative, self.paths))
    1622          if rel_paths:
    1623              log.debug("Saving %s", self.filename)
    1624              lines = self._wrap_lines(rel_paths)
    1625              data = '\n'.join(lines) + '\n'
    1626  
    1627              if os.path.islink(self.filename):
    1628                  os.unlink(self.filename)
    1629              with open(self.filename, 'wt') as f:
    1630                  f.write(data)
    1631  
    1632          elif os.path.exists(self.filename):
    1633              log.debug("Deleting empty %s", self.filename)
    1634              os.unlink(self.filename)
    1635  
    1636          self.dirty = False
    1637  
    1638      @staticmethod
    1639      def _wrap_lines(lines):
    1640          return lines
    1641  
    1642      def add(self, dist):
    1643          """Add `dist` to the distribution map"""
    1644          new_path = (
    1645              dist.location not in self.paths and (
    1646                  dist.location not in self.sitedirs or
    1647                  # account for '.' being in PYTHONPATH
    1648                  dist.location == os.getcwd()
    1649              )
    1650          )
    1651          if new_path:
    1652              self.paths.append(dist.location)
    1653              self.dirty = True
    1654          super().add(dist)
    1655  
    1656      def remove(self, dist):
    1657          """Remove `dist` from the distribution map"""
    1658          while dist.location in self.paths:
    1659              self.paths.remove(dist.location)
    1660              self.dirty = True
    1661          super().remove(dist)
    1662  
    1663      def make_relative(self, path):
    1664          npath, last = os.path.split(normalize_path(path))
    1665          baselen = len(self.basedir)
    1666          parts = [last]
    1667          sep = os.altsep == '/' and '/' or os.sep
    1668          while len(npath) >= baselen:
    1669              if npath == self.basedir:
    1670                  parts.append(os.curdir)
    1671                  parts.reverse()
    1672                  return sep.join(parts)
    1673              npath, last = os.path.split(npath)
    1674              parts.append(last)
    1675          else:
    1676              return path
    1677  
    1678  
    1679  class ESC[4;38;5;81mRewritePthDistributions(ESC[4;38;5;149mPthDistributions):
    1680      @classmethod
    1681      def _wrap_lines(cls, lines):
    1682          yield cls.prelude
    1683          for line in lines:
    1684              yield line
    1685          yield cls.postlude
    1686  
    1687      prelude = _one_liner("""
    1688          import sys
    1689          sys.__plen = len(sys.path)
    1690          """)
    1691      postlude = _one_liner("""
    1692          import sys
    1693          new = sys.path[sys.__plen:]
    1694          del sys.path[sys.__plen:]
    1695          p = getattr(sys, '__egginsert', 0)
    1696          sys.path[p:p] = new
    1697          sys.__egginsert = p + len(new)
    1698          """)
    1699  
    1700  
    1701  if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite':
    1702      PthDistributions = RewritePthDistributions
    1703  
    1704  
    1705  def _first_line_re():
    1706      """
    1707      Return a regular expression based on first_line_re suitable for matching
    1708      strings.
    1709      """
    1710      if isinstance(first_line_re.pattern, str):
    1711          return first_line_re
    1712  
    1713      # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.
    1714      return re.compile(first_line_re.pattern.decode())
    1715  
    1716  
    1717  def auto_chmod(func, arg, exc):
    1718      if func in [os.unlink, os.remove] and os.name == 'nt':
    1719          chmod(arg, stat.S_IWRITE)
    1720          return func(arg)
    1721      et, ev, _ = sys.exc_info()
    1722      # TODO: This code doesn't make sense. What is it trying to do?
    1723      raise (ev[0], ev[1] + (" %s %s" % (func, arg)))
    1724  
    1725  
    1726  def update_dist_caches(dist_path, fix_zipimporter_caches):
    1727      """
    1728      Fix any globally cached `dist_path` related data
    1729  
    1730      `dist_path` should be a path of a newly installed egg distribution (zipped
    1731      or unzipped).
    1732  
    1733      sys.path_importer_cache contains finder objects that have been cached when
    1734      importing data from the original distribution. Any such finders need to be
    1735      cleared since the replacement distribution might be packaged differently,
    1736      e.g. a zipped egg distribution might get replaced with an unzipped egg
    1737      folder or vice versa. Having the old finders cached may then cause Python
    1738      to attempt loading modules from the replacement distribution using an
    1739      incorrect loader.
    1740  
    1741      zipimport.zipimporter objects are Python loaders charged with importing
    1742      data packaged inside zip archives. If stale loaders referencing the
    1743      original distribution, are left behind, they can fail to load modules from
    1744      the replacement distribution. E.g. if an old zipimport.zipimporter instance
    1745      is used to load data from a new zipped egg archive, it may cause the
    1746      operation to attempt to locate the requested data in the wrong location -
    1747      one indicated by the original distribution's zip archive directory
    1748      information. Such an operation may then fail outright, e.g. report having
    1749      read a 'bad local file header', or even worse, it may fail silently &
    1750      return invalid data.
    1751  
    1752      zipimport._zip_directory_cache contains cached zip archive directory
    1753      information for all existing zipimport.zipimporter instances and all such
    1754      instances connected to the same archive share the same cached directory
    1755      information.
    1756  
    1757      If asked, and the underlying Python implementation allows it, we can fix
    1758      all existing zipimport.zipimporter instances instead of having to track
    1759      them down and remove them one by one, by updating their shared cached zip
    1760      archive directory information. This, of course, assumes that the
    1761      replacement distribution is packaged as a zipped egg.
    1762  
    1763      If not asked to fix existing zipimport.zipimporter instances, we still do
    1764      our best to clear any remaining zipimport.zipimporter related cached data
    1765      that might somehow later get used when attempting to load data from the new
    1766      distribution and thus cause such load operations to fail. Note that when
    1767      tracking down such remaining stale data, we can not catch every conceivable
    1768      usage from here, and we clear only those that we know of and have found to
    1769      cause problems if left alive. Any remaining caches should be updated by
    1770      whomever is in charge of maintaining them, i.e. they should be ready to
    1771      handle us replacing their zip archives with new distributions at runtime.
    1772  
    1773      """
    1774      # There are several other known sources of stale zipimport.zipimporter
    1775      # instances that we do not clear here, but might if ever given a reason to
    1776      # do so:
    1777      # * Global setuptools pkg_resources.working_set (a.k.a. 'master working
    1778      # set') may contain distributions which may in turn contain their
    1779      #   zipimport.zipimporter loaders.
    1780      # * Several zipimport.zipimporter loaders held by local variables further
    1781      #   up the function call stack when running the setuptools installation.
    1782      # * Already loaded modules may have their __loader__ attribute set to the
    1783      #   exact loader instance used when importing them. Python 3.4 docs state
    1784      #   that this information is intended mostly for introspection and so is
    1785      #   not expected to cause us problems.
    1786      normalized_path = normalize_path(dist_path)
    1787      _uncache(normalized_path, sys.path_importer_cache)
    1788      if fix_zipimporter_caches:
    1789          _replace_zip_directory_cache_data(normalized_path)
    1790      else:
    1791          # Here, even though we do not want to fix existing and now stale
    1792          # zipimporter cache information, we still want to remove it. Related to
    1793          # Python's zip archive directory information cache, we clear each of
    1794          # its stale entries in two phases:
    1795          #   1. Clear the entry so attempting to access zip archive information
    1796          #      via any existing stale zipimport.zipimporter instances fails.
    1797          #   2. Remove the entry from the cache so any newly constructed
    1798          #      zipimport.zipimporter instances do not end up using old stale
    1799          #      zip archive directory information.
    1800          # This whole stale data removal step does not seem strictly necessary,
    1801          # but has been left in because it was done before we started replacing
    1802          # the zip archive directory information cache content if possible, and
    1803          # there are no relevant unit tests that we can depend on to tell us if
    1804          # this is really needed.
    1805          _remove_and_clear_zip_directory_cache_data(normalized_path)
    1806  
    1807  
    1808  def _collect_zipimporter_cache_entries(normalized_path, cache):
    1809      """
    1810      Return zipimporter cache entry keys related to a given normalized path.
    1811  
    1812      Alternative path spellings (e.g. those using different character case or
    1813      those using alternative path separators) related to the same path are
    1814      included. Any sub-path entries are included as well, i.e. those
    1815      corresponding to zip archives embedded in other zip archives.
    1816  
    1817      """
    1818      result = []
    1819      prefix_len = len(normalized_path)
    1820      for p in cache:
    1821          np = normalize_path(p)
    1822          if (np.startswith(normalized_path) and
    1823                  np[prefix_len:prefix_len + 1] in (os.sep, '')):
    1824              result.append(p)
    1825      return result
    1826  
    1827  
    1828  def _update_zipimporter_cache(normalized_path, cache, updater=None):
    1829      """
    1830      Update zipimporter cache data for a given normalized path.
    1831  
    1832      Any sub-path entries are processed as well, i.e. those corresponding to zip
    1833      archives embedded in other zip archives.
    1834  
    1835      Given updater is a callable taking a cache entry key and the original entry
    1836      (after already removing the entry from the cache), and expected to update
    1837      the entry and possibly return a new one to be inserted in its place.
    1838      Returning None indicates that the entry should not be replaced with a new
    1839      one. If no updater is given, the cache entries are simply removed without
    1840      any additional processing, the same as if the updater simply returned None.
    1841  
    1842      """
    1843      for p in _collect_zipimporter_cache_entries(normalized_path, cache):
    1844          # N.B. pypy's custom zipimport._zip_directory_cache implementation does
    1845          # not support the complete dict interface:
    1846          # * Does not support item assignment, thus not allowing this function
    1847          #    to be used only for removing existing cache entries.
    1848          #  * Does not support the dict.pop() method, forcing us to use the
    1849          #    get/del patterns instead. For more detailed information see the
    1850          #    following links:
    1851          #      https://github.com/pypa/setuptools/issues/202#issuecomment-202913420
    1852          #      http://bit.ly/2h9itJX
    1853          old_entry = cache[p]
    1854          del cache[p]
    1855          new_entry = updater and updater(p, old_entry)
    1856          if new_entry is not None:
    1857              cache[p] = new_entry
    1858  
    1859  
    1860  def _uncache(normalized_path, cache):
    1861      _update_zipimporter_cache(normalized_path, cache)
    1862  
    1863  
    1864  def _remove_and_clear_zip_directory_cache_data(normalized_path):
    1865      def clear_and_remove_cached_zip_archive_directory_data(path, old_entry):
    1866          old_entry.clear()
    1867  
    1868      _update_zipimporter_cache(
    1869          normalized_path, zipimport._zip_directory_cache,
    1870          updater=clear_and_remove_cached_zip_archive_directory_data)
    1871  
    1872  
    1873  # PyPy Python implementation does not allow directly writing to the
    1874  # zipimport._zip_directory_cache and so prevents us from attempting to correct
    1875  # its content. The best we can do there is clear the problematic cache content
    1876  # and have PyPy repopulate it as needed. The downside is that if there are any
    1877  # stale zipimport.zipimporter instances laying around, attempting to use them
    1878  # will fail due to not having its zip archive directory information available
    1879  # instead of being automatically corrected to use the new correct zip archive
    1880  # directory information.
    1881  if '__pypy__' in sys.builtin_module_names:
    1882      _replace_zip_directory_cache_data = \
    1883          _remove_and_clear_zip_directory_cache_data
    1884  else:
    1885  
    1886      def _replace_zip_directory_cache_data(normalized_path):
    1887          def replace_cached_zip_archive_directory_data(path, old_entry):
    1888              # N.B. In theory, we could load the zip directory information just
    1889              # once for all updated path spellings, and then copy it locally and
    1890              # update its contained path strings to contain the correct
    1891              # spelling, but that seems like a way too invasive move (this cache
    1892              # structure is not officially documented anywhere and could in
    1893              # theory change with new Python releases) for no significant
    1894              # benefit.
    1895              old_entry.clear()
    1896              zipimport.zipimporter(path)
    1897              old_entry.update(zipimport._zip_directory_cache[path])
    1898              return old_entry
    1899  
    1900          _update_zipimporter_cache(
    1901              normalized_path, zipimport._zip_directory_cache,
    1902              updater=replace_cached_zip_archive_directory_data)
    1903  
    1904  
    1905  def is_python(text, filename='<string>'):
    1906      "Is this string a valid Python script?"
    1907      try:
    1908          compile(text, filename, 'exec')
    1909      except (SyntaxError, TypeError):
    1910          return False
    1911      else:
    1912          return True
    1913  
    1914  
    1915  def is_sh(executable):
    1916      """Determine if the specified executable is a .sh (contains a #! line)"""
    1917      try:
    1918          with io.open(executable, encoding='latin-1') as fp:
    1919              magic = fp.read(2)
    1920      except (OSError, IOError):
    1921          return executable
    1922      return magic == '#!'
    1923  
    1924  
    1925  def nt_quote_arg(arg):
    1926      """Quote a command line argument according to Windows parsing rules"""
    1927      return subprocess.list2cmdline([arg])
    1928  
    1929  
    1930  def is_python_script(script_text, filename):
    1931      """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc.
    1932      """
    1933      if filename.endswith('.py') or filename.endswith('.pyw'):
    1934          return True  # extension says it's Python
    1935      if is_python(script_text, filename):
    1936          return True  # it's syntactically valid Python
    1937      if script_text.startswith('#!'):
    1938          # It begins with a '#!' line, so check if 'python' is in it somewhere
    1939          return 'python' in script_text.splitlines()[0].lower()
    1940  
    1941      return False  # Not any Python I can recognize
    1942  
    1943  
    1944  try:
    1945      from os import chmod as _chmod
    1946  except ImportError:
    1947      # Jython compatibility
    1948      def _chmod(*args):
    1949          pass
    1950  
    1951  
    1952  def chmod(path, mode):
    1953      log.debug("changing mode of %s to %o", path, mode)
    1954      try:
    1955          _chmod(path, mode)
    1956      except os.error as e:
    1957          log.debug("chmod failed: %s", e)
    1958  
    1959  
    1960  class ESC[4;38;5;81mCommandSpec(ESC[4;38;5;149mlist):
    1961      """
    1962      A command spec for a #! header, specified as a list of arguments akin to
    1963      those passed to Popen.
    1964      """
    1965  
    1966      options = []
    1967      split_args = dict()
    1968  
    1969      @classmethod
    1970      def best(cls):
    1971          """
    1972          Choose the best CommandSpec class based on environmental conditions.
    1973          """
    1974          return cls
    1975  
    1976      @classmethod
    1977      def _sys_executable(cls):
    1978          _default = os.path.normpath(sys.executable)
    1979          return os.environ.get('__PYVENV_LAUNCHER__', _default)
    1980  
    1981      @classmethod
    1982      def from_param(cls, param):
    1983          """
    1984          Construct a CommandSpec from a parameter to build_scripts, which may
    1985          be None.
    1986          """
    1987          if isinstance(param, cls):
    1988              return param
    1989          if isinstance(param, list):
    1990              return cls(param)
    1991          if param is None:
    1992              return cls.from_environment()
    1993          # otherwise, assume it's a string.
    1994          return cls.from_string(param)
    1995  
    1996      @classmethod
    1997      def from_environment(cls):
    1998          return cls([cls._sys_executable()])
    1999  
    2000      @classmethod
    2001      def from_string(cls, string):
    2002          """
    2003          Construct a command spec from a simple string representing a command
    2004          line parseable by shlex.split.
    2005          """
    2006          items = shlex.split(string, **cls.split_args)
    2007          return cls(items)
    2008  
    2009      def install_options(self, script_text):
    2010          self.options = shlex.split(self._extract_options(script_text))
    2011          cmdline = subprocess.list2cmdline(self)
    2012          if not isascii(cmdline):
    2013              self.options[:0] = ['-x']
    2014  
    2015      @staticmethod
    2016      def _extract_options(orig_script):
    2017          """
    2018          Extract any options from the first line of the script.
    2019          """
    2020          first = (orig_script + '\n').splitlines()[0]
    2021          match = _first_line_re().match(first)
    2022          options = match.group(1) or '' if match else ''
    2023          return options.strip()
    2024  
    2025      def as_header(self):
    2026          return self._render(self + list(self.options))
    2027  
    2028      @staticmethod
    2029      def _strip_quotes(item):
    2030          _QUOTES = '"\''
    2031          for q in _QUOTES:
    2032              if item.startswith(q) and item.endswith(q):
    2033                  return item[1:-1]
    2034          return item
    2035  
    2036      @staticmethod
    2037      def _render(items):
    2038          cmdline = subprocess.list2cmdline(
    2039              CommandSpec._strip_quotes(item.strip()) for item in items)
    2040          return '#!' + cmdline + '\n'
    2041  
    2042  
    2043  # For pbr compat; will be removed in a future version.
    2044  sys_executable = CommandSpec._sys_executable()
    2045  
    2046  
    2047  class ESC[4;38;5;81mWindowsCommandSpec(ESC[4;38;5;149mCommandSpec):
    2048      split_args = dict(posix=False)
    2049  
    2050  
    2051  class ESC[4;38;5;81mScriptWriter:
    2052      """
    2053      Encapsulates behavior around writing entry point scripts for console and
    2054      gui apps.
    2055      """
    2056  
    2057      template = textwrap.dedent(r"""
    2058          # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
    2059          import re
    2060          import sys
    2061  
    2062          # for compatibility with easy_install; see #2198
    2063          __requires__ = %(spec)r
    2064  
    2065          try:
    2066              from importlib.metadata import distribution
    2067          except ImportError:
    2068              try:
    2069                  from importlib_metadata import distribution
    2070              except ImportError:
    2071                  from pkg_resources import load_entry_point
    2072  
    2073  
    2074          def importlib_load_entry_point(spec, group, name):
    2075              dist_name, _, _ = spec.partition('==')
    2076              matches = (
    2077                  entry_point
    2078                  for entry_point in distribution(dist_name).entry_points
    2079                  if entry_point.group == group and entry_point.name == name
    2080              )
    2081              return next(matches).load()
    2082  
    2083  
    2084          globals().setdefault('load_entry_point', importlib_load_entry_point)
    2085  
    2086  
    2087          if __name__ == '__main__':
    2088              sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    2089              sys.exit(load_entry_point(%(spec)r, %(group)r, %(name)r)())
    2090          """).lstrip()
    2091  
    2092      command_spec_class = CommandSpec
    2093  
    2094      @classmethod
    2095      def get_script_args(cls, dist, executable=None, wininst=False):
    2096          # for backward compatibility
    2097          warnings.warn("Use get_args", EasyInstallDeprecationWarning)
    2098          writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
    2099          header = cls.get_script_header("", executable, wininst)
    2100          return writer.get_args(dist, header)
    2101  
    2102      @classmethod
    2103      def get_script_header(cls, script_text, executable=None, wininst=False):
    2104          # for backward compatibility
    2105          warnings.warn(
    2106              "Use get_header", EasyInstallDeprecationWarning, stacklevel=2)
    2107          if wininst:
    2108              executable = "python.exe"
    2109          return cls.get_header(script_text, executable)
    2110  
    2111      @classmethod
    2112      def get_args(cls, dist, header=None):
    2113          """
    2114          Yield write_script() argument tuples for a distribution's
    2115          console_scripts and gui_scripts entry points.
    2116          """
    2117          if header is None:
    2118              header = cls.get_header()
    2119          spec = str(dist.as_requirement())
    2120          for type_ in 'console', 'gui':
    2121              group = type_ + '_scripts'
    2122              for name, ep in dist.get_entry_map(group).items():
    2123                  cls._ensure_safe_name(name)
    2124                  script_text = cls.template % locals()
    2125                  args = cls._get_script_args(type_, name, header, script_text)
    2126                  for res in args:
    2127                      yield res
    2128  
    2129      @staticmethod
    2130      def _ensure_safe_name(name):
    2131          """
    2132          Prevent paths in *_scripts entry point names.
    2133          """
    2134          has_path_sep = re.search(r'[\\/]', name)
    2135          if has_path_sep:
    2136              raise ValueError("Path separators not allowed in script names")
    2137  
    2138      @classmethod
    2139      def get_writer(cls, force_windows):
    2140          # for backward compatibility
    2141          warnings.warn("Use best", EasyInstallDeprecationWarning)
    2142          return WindowsScriptWriter.best() if force_windows else cls.best()
    2143  
    2144      @classmethod
    2145      def best(cls):
    2146          """
    2147          Select the best ScriptWriter for this environment.
    2148          """
    2149          if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'):
    2150              return WindowsScriptWriter.best()
    2151          else:
    2152              return cls
    2153  
    2154      @classmethod
    2155      def _get_script_args(cls, type_, name, header, script_text):
    2156          # Simply write the stub with no extension.
    2157          yield (name, header + script_text)
    2158  
    2159      @classmethod
    2160      def get_header(cls, script_text="", executable=None):
    2161          """Create a #! line, getting options (if any) from script_text"""
    2162          cmd = cls.command_spec_class.best().from_param(executable)
    2163          cmd.install_options(script_text)
    2164          return cmd.as_header()
    2165  
    2166  
    2167  class ESC[4;38;5;81mWindowsScriptWriter(ESC[4;38;5;149mScriptWriter):
    2168      command_spec_class = WindowsCommandSpec
    2169  
    2170      @classmethod
    2171      def get_writer(cls):
    2172          # for backward compatibility
    2173          warnings.warn("Use best", EasyInstallDeprecationWarning)
    2174          return cls.best()
    2175  
    2176      @classmethod
    2177      def best(cls):
    2178          """
    2179          Select the best ScriptWriter suitable for Windows
    2180          """
    2181          writer_lookup = dict(
    2182              executable=WindowsExecutableLauncherWriter,
    2183              natural=cls,
    2184          )
    2185          # for compatibility, use the executable launcher by default
    2186          launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')
    2187          return writer_lookup[launcher]
    2188  
    2189      @classmethod
    2190      def _get_script_args(cls, type_, name, header, script_text):
    2191          "For Windows, add a .py extension"
    2192          ext = dict(console='.pya', gui='.pyw')[type_]
    2193          if ext not in os.environ['PATHEXT'].lower().split(';'):
    2194              msg = (
    2195                  "{ext} not listed in PATHEXT; scripts will not be "
    2196                  "recognized as executables."
    2197              ).format(**locals())
    2198              warnings.warn(msg, UserWarning)
    2199          old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
    2200          old.remove(ext)
    2201          header = cls._adjust_header(type_, header)
    2202          blockers = [name + x for x in old]
    2203          yield name + ext, header + script_text, 't', blockers
    2204  
    2205      @classmethod
    2206      def _adjust_header(cls, type_, orig_header):
    2207          """
    2208          Make sure 'pythonw' is used for gui and 'python' is used for
    2209          console (regardless of what sys.executable is).
    2210          """
    2211          pattern = 'pythonw.exe'
    2212          repl = 'python.exe'
    2213          if type_ == 'gui':
    2214              pattern, repl = repl, pattern
    2215          pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)
    2216          new_header = pattern_ob.sub(string=orig_header, repl=repl)
    2217          return new_header if cls._use_header(new_header) else orig_header
    2218  
    2219      @staticmethod
    2220      def _use_header(new_header):
    2221          """
    2222          Should _adjust_header use the replaced header?
    2223  
    2224          On non-windows systems, always use. On
    2225          Windows systems, only use the replaced header if it resolves
    2226          to an executable on the system.
    2227          """
    2228          clean_header = new_header[2:-1].strip('"')
    2229          return sys.platform != 'win32' or find_executable(clean_header)
    2230  
    2231  
    2232  class ESC[4;38;5;81mWindowsExecutableLauncherWriter(ESC[4;38;5;149mWindowsScriptWriter):
    2233      @classmethod
    2234      def _get_script_args(cls, type_, name, header, script_text):
    2235          """
    2236          For Windows, add a .py extension and an .exe launcher
    2237          """
    2238          if type_ == 'gui':
    2239              launcher_type = 'gui'
    2240              ext = '-script.pyw'
    2241              old = ['.pyw']
    2242          else:
    2243              launcher_type = 'cli'
    2244              ext = '-script.py'
    2245              old = ['.py', '.pyc', '.pyo']
    2246          hdr = cls._adjust_header(type_, header)
    2247          blockers = [name + x for x in old]
    2248          yield (name + ext, hdr + script_text, 't', blockers)
    2249          yield (
    2250              name + '.exe', get_win_launcher(launcher_type),
    2251              'b'  # write in binary mode
    2252          )
    2253          if not is_64bit():
    2254              # install a manifest for the launcher to prevent Windows
    2255              # from detecting it as an installer (which it will for
    2256              #  launchers like easy_install.exe). Consider only
    2257              #  adding a manifest for launchers detected as installers.
    2258              #  See Distribute #143 for details.
    2259              m_name = name + '.exe.manifest'
    2260              yield (m_name, load_launcher_manifest(name), 't')
    2261  
    2262  
    2263  # for backward-compatibility
    2264  get_script_args = ScriptWriter.get_script_args
    2265  get_script_header = ScriptWriter.get_script_header
    2266  
    2267  
    2268  def get_win_launcher(type):
    2269      """
    2270      Load the Windows launcher (executable) suitable for launching a script.
    2271  
    2272      `type` should be either 'cli' or 'gui'
    2273  
    2274      Returns the executable as a byte string.
    2275      """
    2276      launcher_fn = '%s.exe' % type
    2277      if is_64bit():
    2278          if get_platform() == "win-arm64":
    2279              launcher_fn = launcher_fn.replace(".", "-arm64.")
    2280          else:
    2281              launcher_fn = launcher_fn.replace(".", "-64.")
    2282      else:
    2283          launcher_fn = launcher_fn.replace(".", "-32.")
    2284      return resource_string('setuptools', launcher_fn)
    2285  
    2286  
    2287  def load_launcher_manifest(name):
    2288      manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml')
    2289      return manifest.decode('utf-8') % vars()
    2290  
    2291  
    2292  def rmtree(path, ignore_errors=False, onerror=auto_chmod):
    2293      return shutil.rmtree(path, ignore_errors, onerror)
    2294  
    2295  
    2296  def current_umask():
    2297      tmp = os.umask(0o022)
    2298      os.umask(tmp)
    2299      return tmp
    2300  
    2301  
    2302  def only_strs(values):
    2303      """
    2304      Exclude non-str values. Ref #3063.
    2305      """
    2306      return filter(lambda val: isinstance(val, str), values)
    2307  
    2308  
    2309  class ESC[4;38;5;81mEasyInstallDeprecationWarning(ESC[4;38;5;149mSetuptoolsDeprecationWarning):
    2310      """
    2311      Warning for EasyInstall deprecations, bypassing suppression.
    2312      """