codekingpro/portable-devtools
114k
1"""2Easy Install3------------4 5A tool for doing automatic download/extract/build of distutils-based Python6packages. For detailed documentation, see the accompanying EasyInstall.txt7file, or visit the `EasyInstall home page`__.8 9__ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html10 11"""12 13from glob import glob14from distutils.util import get_platform15from distutils.util import convert_path, subst_vars16from distutils.errors import (17 DistutilsArgError,18 DistutilsOptionError,19 DistutilsError,20 DistutilsPlatformError,21)22from distutils import log, dir_util23from distutils.command.build_scripts import first_line_re24from distutils.spawn import find_executable25from distutils.command import install26import sys27import os28from typing import Dict, List29import zipimport30import shutil31import tempfile32import zipfile33import re34import stat35import random36import textwrap37import warnings38import site39import struct40import contextlib41import subprocess42import shlex43import io44import configparser45import sysconfig46 47from sysconfig import get_path48 49from setuptools import Command50from setuptools.sandbox import run_setup51from setuptools.command import setopt52from setuptools.archive_util import unpack_archive53from setuptools.package_index import (54 PackageIndex,55 parse_requirement_arg,56 URL_SCHEME,57)58from setuptools.command import bdist_egg, egg_info59from setuptools.warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning60from setuptools.wheel import Wheel61from pkg_resources import (62 normalize_path,63 resource_string,64 get_distribution,65 find_distributions,66 Environment,67 Requirement,68 Distribution,69 PathMetadata,70 EggMetadata,71 WorkingSet,72 DistributionNotFound,73 VersionConflict,74 DEVELOP_DIST,75)76import pkg_resources77from ..compat import py39, py31178from .._path import ensure_directory79from ..extern.jaraco.text import yield_lines80 81 82# Turn on PEP440Warnings83warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)84 85__all__ = [86 'easy_install',87 'PthDistributions',88 'extract_wininst_cfg',89 'get_exe_prefixes',90]91 92 93def is_64bit():94 return struct.calcsize("P") == 895 96 97def _to_bytes(s):98 return s.encode('utf8')99 100 101def isascii(s):102 try:103 s.encode('ascii')104 return True105 except UnicodeError:106 return False107 108 109def _one_liner(text):110 return textwrap.dedent(text).strip().replace('\n', '; ')111 112 113class easy_install(Command):114 """Manage a download/build/install process"""115 116 description = "Find/get/install Python packages"117 command_consumes_arguments = True118 119 user_options = [120 ('prefix=', None, "installation prefix"),121 ("zip-ok", "z", "install package as a zipfile"),122 ("multi-version", "m", "make apps have to require() a version"),123 ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"),124 ("install-dir=", "d", "install package to DIR"),125 ("script-dir=", "s", "install scripts to DIR"),126 ("exclude-scripts", "x", "Don't install scripts"),127 ("always-copy", "a", "Copy all needed packages to install dir"),128 ("index-url=", "i", "base URL of Python Package Index"),129 ("find-links=", "f", "additional URL(s) to search for packages"),130 ("build-directory=", "b", "download/extract/build in DIR; keep the results"),131 (132 'optimize=',133 'O',134 "also compile with optimization: -O1 for \"python -O\", "135 "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",136 ),137 ('record=', None, "filename in which to record list of installed files"),138 ('always-unzip', 'Z', "don't install as a zipfile, no matter what"),139 ('site-dirs=', 'S', "list of directories where .pth files work"),140 ('editable', 'e', "Install specified packages in editable form"),141 ('no-deps', 'N', "don't install dependencies"),142 ('allow-hosts=', 'H', "pattern(s) that hostnames must match"),143 ('local-snapshots-ok', 'l', "allow building eggs from local checkouts"),144 ('version', None, "print version information and exit"),145 (146 'no-find-links',147 None,148 "Don't load find-links defined in packages being installed",149 ),150 ('user', None, "install in user site-package '%s'" % site.USER_SITE),151 ]152 boolean_options = [153 'zip-ok',154 'multi-version',155 'exclude-scripts',156 'upgrade',157 'always-copy',158 'editable',159 'no-deps',160 'local-snapshots-ok',161 'version',162 'user',163 ]164 165 negative_opt = {'always-unzip': 'zip-ok'}166 create_index = PackageIndex167 168 def initialize_options(self):169 EasyInstallDeprecationWarning.emit()170 171 # the --user option seems to be an opt-in one,172 # so the default should be False.173 self.user = 0174 self.zip_ok = self.local_snapshots_ok = None175 self.install_dir = self.script_dir = self.exclude_scripts = None176 self.index_url = None177 self.find_links = None178 self.build_directory = None179 self.args = None180 self.optimize = self.record = None181 self.upgrade = self.always_copy = self.multi_version = None182 self.editable = self.no_deps = self.allow_hosts = None183 self.root = self.prefix = self.no_report = None184 self.version = None185 self.install_purelib = None # for pure module distributions186 self.install_platlib = None # non-pure (dists w/ extensions)187 self.install_headers = None # for C/C++ headers188 self.install_lib = None # set to either purelib or platlib189 self.install_scripts = None190 self.install_data = None191 self.install_base = None192 self.install_platbase = None193 self.install_userbase = site.USER_BASE194 self.install_usersite = site.USER_SITE195 self.no_find_links = None196 197 # Options not specifiable via command line198 self.package_index = None199 self.pth_file = self.always_copy_from = None200 self.site_dirs = None201 self.installed_projects = {}202 # Always read easy_install options, even if we are subclassed, or have203 # an independent instance created. This ensures that defaults will204 # always come from the standard configuration file(s)' "easy_install"205 # section, even if this is a "develop" or "install" command, or some206 # other embedding.207 self._dry_run = None208 self.verbose = self.distribution.verbose209 self.distribution._set_command_options(210 self, self.distribution.get_option_dict('easy_install')211 )212 213 def delete_blockers(self, blockers):214 extant_blockers = (215 filename216 for filename in blockers217 if os.path.exists(filename) or os.path.islink(filename)218 )219 list(map(self._delete_path, extant_blockers))220 221 def _delete_path(self, path):222 log.info("Deleting %s", path)223 if self.dry_run:224 return225 226 is_tree = os.path.isdir(path) and not os.path.islink(path)227 remover = _rmtree if is_tree else os.unlink228 remover(path)229 230 @staticmethod231 def _render_version():232 """233 Render the Setuptools version and installation details, then exit.234 """235 ver = '{}.{}'.format(*sys.version_info)236 dist = get_distribution('setuptools')237 tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'238 print(tmpl.format(**locals()))239 raise SystemExit()240 241 def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME242 self.version and self._render_version()243 244 py_version = sys.version.split()[0]245 246 self.config_vars = dict(sysconfig.get_config_vars())247 248 self.config_vars.update({249 'dist_name': self.distribution.get_name(),250 'dist_version': self.distribution.get_version(),251 'dist_fullname': self.distribution.get_fullname(),252 'py_version': py_version,253 'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',254 'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',255 'sys_prefix': self.config_vars['prefix'],256 'sys_exec_prefix': self.config_vars['exec_prefix'],257 # Only POSIX systems have abiflags258 'abiflags': getattr(sys, 'abiflags', ''),259 # Only python 3.9+ has platlibdir260 'platlibdir': getattr(sys, 'platlibdir', 'lib'),261 })262 with contextlib.suppress(AttributeError):263 # only for distutils outside stdlib264 self.config_vars.update({265 'implementation_lower': install._get_implementation().lower(),266 'implementation': install._get_implementation(),267 })268 269 # pypa/distutils#113 Python 3.9 compat270 self.config_vars.setdefault(271 'py_version_nodot_plat',272 getattr(sys, 'windir', '').replace('.', ''),273 )274 275 self.config_vars['userbase'] = self.install_userbase276 self.config_vars['usersite'] = self.install_usersite277 if self.user and not site.ENABLE_USER_SITE:278 log.warn("WARNING: The user site-packages directory is disabled.")279 280 self._fix_install_dir_for_user_site()281 282 self.expand_basedirs()283 self.expand_dirs()284 285 self._expand(286 'install_dir',287 'script_dir',288 'build_directory',289 'site_dirs',290 )291 # If a non-default installation directory was specified, default the292 # script directory to match it.293 if self.script_dir is None:294 self.script_dir = self.install_dir295 296 if self.no_find_links is None:297 self.no_find_links = False298 299 # Let install_dir get set by install_lib command, which in turn300 # gets its info from the install command, and takes into account301 # --prefix and --home and all that other crud.302 self.set_undefined_options('install_lib', ('install_dir', 'install_dir'))303 # Likewise, set default script_dir from 'install_scripts.install_dir'304 self.set_undefined_options('install_scripts', ('install_dir', 'script_dir'))305 306 if self.user and self.install_purelib:307 self.install_dir = self.install_purelib308 self.script_dir = self.install_scripts309 # default --record from the install command310 self.set_undefined_options('install', ('record', 'record'))311 self.all_site_dirs = get_site_dirs()312 self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))313 314 if not self.editable:315 self.check_site_dir()316 default_index = os.getenv("__EASYINSTALL_INDEX", "https://pypi.org/simple/")317 # ^ Private API for testing purposes only318 self.index_url = self.index_url or default_index319 self.shadow_path = self.all_site_dirs[:]320 for path_item in self.install_dir, normalize_path(self.script_dir):321 if path_item not in self.shadow_path:322 self.shadow_path.insert(0, path_item)323 324 if self.allow_hosts is not None:325 hosts = [s.strip() for s in self.allow_hosts.split(',')]326 else:327 hosts = ['*']328 if self.package_index is None:329 self.package_index = self.create_index(330 self.index_url,331 search_path=self.shadow_path,332 hosts=hosts,333 )334 self.local_index = Environment(self.shadow_path + sys.path)335 336 if self.find_links is not None:337 if isinstance(self.find_links, str):338 self.find_links = self.find_links.split()339 else:340 self.find_links = []341 if self.local_snapshots_ok:342 self.package_index.scan_egg_links(self.shadow_path + sys.path)343 if not self.no_find_links:344 self.package_index.add_find_links(self.find_links)345 self.set_undefined_options('install_lib', ('optimize', 'optimize'))346 self.optimize = self._validate_optimize(self.optimize)347 348 if self.editable and not self.build_directory:349 raise DistutilsArgError(350 "Must specify a build directory (-b) when using --editable"351 )352 if not self.args:353 raise DistutilsArgError(354 "No urls, filenames, or requirements specified (see --help)"355 )356 357 self.outputs = []358 359 @staticmethod360 def _process_site_dirs(site_dirs):361 if site_dirs is None:362 return363 364 normpath = map(normalize_path, sys.path)365 site_dirs = [os.path.expanduser(s.strip()) for s in site_dirs.split(',')]366 for d in site_dirs:367 if not os.path.isdir(d):368 log.warn("%s (in --site-dirs) does not exist", d)369 elif normalize_path(d) not in normpath:370 raise DistutilsOptionError(d + " (in --site-dirs) is not on sys.path")371 else:372 yield normalize_path(d)373 374 @staticmethod375 def _validate_optimize(value):376 try:377 value = int(value)378 if value not in range(3):379 raise ValueError380 except ValueError as e:381 raise DistutilsOptionError("--optimize must be 0, 1, or 2") from e382 383 return value384 385 def _fix_install_dir_for_user_site(self):386 """387 Fix the install_dir if "--user" was used.388 """389 if not self.user:390 return391 392 self.create_home_path()393 if self.install_userbase is None:394 msg = "User base directory is not specified"395 raise DistutilsPlatformError(msg)396 self.install_base = self.install_platbase = self.install_userbase397 scheme_name = f'{os.name}_user'398 self.select_scheme(scheme_name)399 400 def _expand_attrs(self, attrs):401 for attr in attrs:402 val = getattr(self, attr)403 if val is not None:404 if os.name == 'posix' or os.name == 'nt':405 val = os.path.expanduser(val)406 val = subst_vars(val, self.config_vars)407 setattr(self, attr, val)408 409 def expand_basedirs(self):410 """Calls `os.path.expanduser` on install_base, install_platbase and411 root."""412 self._expand_attrs(['install_base', 'install_platbase', 'root'])413 414 def expand_dirs(self):415 """Calls `os.path.expanduser` on install dirs."""416 dirs = [417 'install_purelib',418 'install_platlib',419 'install_lib',420 'install_headers',421 'install_scripts',422 'install_data',423 ]424 self._expand_attrs(dirs)425 426 def run(self, show_deprecation=True):427 if show_deprecation:428 self.announce(429 "WARNING: The easy_install command is deprecated "430 "and will be removed in a future version.",431 log.WARN,432 )433 if self.verbose != self.distribution.verbose:434 log.set_verbosity(self.verbose)435 try:436 for spec in self.args:437 self.easy_install(spec, not self.no_deps)438 if self.record:439 outputs = self.outputs440 if self.root: # strip any package prefix441 root_len = len(self.root)442 for counter in range(len(outputs)):443 outputs[counter] = outputs[counter][root_len:]444 from distutils import file_util445 446 self.execute(447 file_util.write_file,448 (self.record, outputs),449 "writing list of installed files to '%s'" % self.record,450 )451 self.warn_deprecated_options()452 finally:453 log.set_verbosity(self.distribution.verbose)454 455 def pseudo_tempname(self):456 """Return a pseudo-tempname base in the install directory.457 This code is intentionally naive; if a malicious party can write to458 the target directory you're already in deep doodoo.459 """460 try:461 pid = os.getpid()462 except Exception:463 pid = random.randint(0, sys.maxsize)464 return os.path.join(self.install_dir, "test-easy-install-%s" % pid)465 466 def warn_deprecated_options(self):467 pass468 469 def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME470 """Verify that self.install_dir is .pth-capable dir, if needed"""471 472 instdir = normalize_path(self.install_dir)473 pth_file = os.path.join(instdir, 'easy-install.pth')474 475 if not os.path.exists(instdir):476 try:477 os.makedirs(instdir)478 except OSError:479 self.cant_write_to_target()480 481 # Is it a configured, PYTHONPATH, implicit, or explicit site dir?482 is_site_dir = instdir in self.all_site_dirs483 484 if not is_site_dir and not self.multi_version:485 # No? Then directly test whether it does .pth file processing486 is_site_dir = self.check_pth_processing()487 else:488 # make sure we can write to target dir489 testfile = self.pseudo_tempname() + '.write-test'490 test_exists = os.path.exists(testfile)491 try:492 if test_exists:493 os.unlink(testfile)494 open(testfile, 'wb').close()495 os.unlink(testfile)496 except OSError:497 self.cant_write_to_target()498 499 if not is_site_dir and not self.multi_version:500 # Can't install non-multi to non-site dir with easy_install501 pythonpath = os.environ.get('PYTHONPATH', '')502 log.warn(self.__no_default_msg, self.install_dir, pythonpath)503 504 if is_site_dir:505 if self.pth_file is None:506 self.pth_file = PthDistributions(pth_file, self.all_site_dirs)507 else:508 self.pth_file = None509 510 if self.multi_version and not os.path.exists(pth_file):511 self.pth_file = None # don't create a .pth file512 self.install_dir = instdir513 514 __cant_write_msg = textwrap.dedent(515 """516 can't create or remove files in install directory517 518 The following error occurred while trying to add or remove files in the519 installation directory:520 521 %s522 523 The installation directory you specified (via --install-dir, --prefix, or524 the distutils default setting) was:525 526 %s527 """528 ).lstrip() # noqa529 530 __not_exists_id = textwrap.dedent(531 """532 This directory does not currently exist. Please create it and try again, or533 choose a different installation directory (using the -d or --install-dir534 option).535 """536 ).lstrip() # noqa537 538 __access_msg = textwrap.dedent(539 """540 Perhaps your account does not have write access to this directory? If the541 installation directory is a system-owned directory, you may need to sign in542 as the administrator or "root" account. If you do not have administrative543 access to this machine, you may wish to choose a different installation544 directory, preferably one that is listed in your PYTHONPATH environment545 variable.546 547 For information on other options, you may wish to consult the548 documentation at:549 550 https://setuptools.pypa.io/en/latest/deprecated/easy_install.html551 552 Please make the appropriate changes for your system and try again.553 """554 ).lstrip() # noqa555 556 def cant_write_to_target(self):557 msg = self.__cant_write_msg % (558 sys.exc_info()[1],559 self.install_dir,560 )561 562 if not os.path.exists(self.install_dir):563 msg += '\n' + self.__not_exists_id564 else:565 msg += '\n' + self.__access_msg566 raise DistutilsError(msg)567 568 def check_pth_processing(self): # noqa: C901569 """Empirically verify whether .pth files are supported in inst. dir"""570 instdir = self.install_dir571 log.info("Checking .pth file support in %s", instdir)572 pth_file = self.pseudo_tempname() + ".pth"573 ok_file = pth_file + '.ok'574 ok_exists = os.path.exists(ok_file)575 tmpl = (576 _one_liner(577 """578 import os579 f = open({ok_file!r}, 'w', encoding="utf-8")580 f.write('OK')581 f.close()582 """583 )584 + '\n'585 )586 try:587 if ok_exists:588 os.unlink(ok_file)589 dirname = os.path.dirname(ok_file)590 os.makedirs(dirname, exist_ok=True)591 f = open(pth_file, 'w', encoding=py39.LOCALE_ENCODING)592 # ^-- Requires encoding="locale" instead of "utf-8" (python/cpython#77102).593 except OSError:594 self.cant_write_to_target()595 else:596 try:597 f.write(tmpl.format(**locals()))598 f.close()599 f = None600 executable = sys.executable601 if os.name == 'nt':602 dirname, basename = os.path.split(executable)603 alt = os.path.join(dirname, 'pythonw.exe')604 use_alt = basename.lower() == 'python.exe' and os.path.exists(alt)605 if use_alt:606 # use pythonw.exe to avoid opening a console window607 executable = alt608 609 from distutils.spawn import spawn610 611 spawn([executable, '-E', '-c', 'pass'], 0)612 613 if os.path.exists(ok_file):614 log.info("TEST PASSED: %s appears to support .pth files", instdir)615 return True616 finally:617 if f:618 f.close()619 if os.path.exists(ok_file):620 os.unlink(ok_file)621 if os.path.exists(pth_file):622 os.unlink(pth_file)623 if not self.multi_version:624 log.warn("TEST FAILED: %s does NOT support .pth files", instdir)625 return False626 627 def install_egg_scripts(self, dist):628 """Write all the scripts for `dist`, unless scripts are excluded"""629 if not self.exclude_scripts and dist.metadata_isdir('scripts'):630 for script_name in dist.metadata_listdir('scripts'):631 if dist.metadata_isdir('scripts/' + script_name):632 # The "script" is a directory, likely a Python 3633 # __pycache__ directory, so skip it.634 continue635 self.install_script(636 dist, script_name, dist.get_metadata('scripts/' + script_name)637 )638 self.install_wrapper_scripts(dist)639 640 def add_output(self, path):641 if os.path.isdir(path):642 for base, dirs, files in os.walk(path):643 for filename in files:644 self.outputs.append(os.path.join(base, filename))645 else:646 self.outputs.append(path)647 648 def not_editable(self, spec):649 if self.editable:650 raise DistutilsArgError(651 "Invalid argument %r: you can't use filenames or URLs "652 "with --editable (except via the --find-links option)." % (spec,)653 )654 655 def check_editable(self, spec):656 if not self.editable:657 return658 659 if os.path.exists(os.path.join(self.build_directory, spec.key)):660 raise DistutilsArgError(661 "%r already exists in %s; can't do a checkout there"662 % (spec.key, self.build_directory)663 )664 665 @contextlib.contextmanager666 def _tmpdir(self):667 tmpdir = tempfile.mkdtemp(prefix="easy_install-")668 try:669 # cast to str as workaround for #709 and #710 and #712670 yield str(tmpdir)671 finally:672 os.path.exists(tmpdir) and _rmtree(tmpdir)673 674 def easy_install(self, spec, deps=False):675 with self._tmpdir() as tmpdir:676 if not isinstance(spec, Requirement):677 if URL_SCHEME(spec):678 # It's a url, download it to tmpdir and process679 self.not_editable(spec)680 dl = self.package_index.download(spec, tmpdir)681 return self.install_item(None, dl, tmpdir, deps, True)682 683 elif os.path.exists(spec):684 # Existing file or directory, just process it directly685 self.not_editable(spec)686 return self.install_item(None, spec, tmpdir, deps, True)687 else:688 spec = parse_requirement_arg(spec)689 690 self.check_editable(spec)691 dist = self.package_index.fetch_distribution(692 spec,693 tmpdir,694 self.upgrade,695 self.editable,696 not self.always_copy,697 self.local_index,698 )699 if dist is None:700 msg = "Could not find suitable distribution for %r" % spec701 if self.always_copy:702 msg += " (--always-copy skips system and development eggs)"703 raise DistutilsError(msg)704 elif dist.precedence == DEVELOP_DIST:705 # .egg-info dists don't need installing, just process deps706 self.process_distribution(spec, dist, deps, "Using")707 return dist708 else:709 return self.install_item(spec, dist.location, tmpdir, deps)710 711 def install_item(self, spec, download, tmpdir, deps, install_needed=False):712 # Installation is also needed if file in tmpdir or is not an egg713 install_needed = install_needed or self.always_copy714 install_needed = install_needed or os.path.dirname(download) == tmpdir715 install_needed = install_needed or not download.endswith('.egg')716 install_needed = install_needed or (717 self.always_copy_from is not None718 and os.path.dirname(normalize_path(download))719 == normalize_path(self.always_copy_from)720 )721 722 if spec and not install_needed:723 # at this point, we know it's a local .egg, we just don't know if724 # it's already installed.725 for dist in self.local_index[spec.project_name]:726 if dist.location == download:727 break728 else:729 install_needed = True # it's not in the local index730 731 log.info("Processing %s", os.path.basename(download))732 733 if install_needed:734 dists = self.install_eggs(spec, download, tmpdir)735 for dist in dists:736 self.process_distribution(spec, dist, deps)737 else:738 dists = [self.egg_distribution(download)]739 self.process_distribution(spec, dists[0], deps, "Using")740 741 if spec is not None:742 for dist in dists:743 if dist in spec:744 return dist745 return None746 747 def select_scheme(self, name):748 try:749 install._select_scheme(self, name)750 except AttributeError:751 # stdlib distutils752 install.install.select_scheme(self, name.replace('posix', 'unix'))753 754 # FIXME: 'easy_install.process_distribution' is too complex (12)755 def process_distribution( # noqa: C901756 self,757 requirement,758 dist,759 deps=True,760 *info,761 ):762 self.update_pth(dist)763 self.package_index.add(dist)764 if dist in self.local_index[dist.key]:765 self.local_index.remove(dist)766 self.local_index.add(dist)767 self.install_egg_scripts(dist)768 self.installed_projects[dist.key] = dist769 log.info(self.installation_report(requirement, dist, *info))770 if dist.has_metadata('dependency_links.txt') and not self.no_find_links:771 self.package_index.add_find_links(772 dist.get_metadata_lines('dependency_links.txt')773 )774 if not deps and not self.always_copy:775 return776 elif requirement is not None and dist.key != requirement.key:777 log.warn("Skipping dependencies for %s", dist)778 return # XXX this is not the distribution we were looking for779 elif requirement is None or dist not in requirement:780 # if we wound up with a different version, resolve what we've got781 distreq = dist.as_requirement()782 requirement = Requirement(str(distreq))783 log.info("Processing dependencies for %s", requirement)784 try:785 distros = WorkingSet([]).resolve(786 [requirement], self.local_index, self.easy_install787 )788 except DistributionNotFound as e:789 raise DistutilsError(str(e)) from e790 except VersionConflict as e:791 raise DistutilsError(e.report()) from e792 if self.always_copy or self.always_copy_from:793 # Force all the relevant distros to be copied or activated794 for dist in distros:795 if dist.key not in self.installed_projects:796 self.easy_install(dist.as_requirement())797 log.info("Finished processing dependencies for %s", requirement)798 799 def should_unzip(self, dist):800 if self.zip_ok is not None:801 return not self.zip_ok802 if dist.has_metadata('not-zip-safe'):803 return True804 if not dist.has_metadata('zip-safe'):805 return True806 return False807 808 def maybe_move(self, spec, dist_filename, setup_base):809 dst = os.path.join(self.build_directory, spec.key)810 if os.path.exists(dst):811 msg = "%r already exists in %s; build directory %s will not be kept"812 log.warn(msg, spec.key, self.build_directory, setup_base)813 return setup_base814 if os.path.isdir(dist_filename):815 setup_base = dist_filename816 else:817 if os.path.dirname(dist_filename) == setup_base:818 os.unlink(dist_filename) # get it out of the tmp dir819 contents = os.listdir(setup_base)820 if len(contents) == 1:821 dist_filename = os.path.join(setup_base, contents[0])822 if os.path.isdir(dist_filename):823 # if the only thing there is a directory, move it instead824 setup_base = dist_filename825 ensure_directory(dst)826 shutil.move(setup_base, dst)827 return dst828 829 def install_wrapper_scripts(self, dist):830 if self.exclude_scripts:831 return832 for args in ScriptWriter.best().get_args(dist):833 self.write_script(*args)834 835 def install_script(self, dist, script_name, script_text, dev_path=None):836 """Generate a legacy script wrapper and install it"""837 spec = str(dist.as_requirement())838 is_script = is_python_script(script_text, script_name)839 840 if is_script:841 body = self._load_template(dev_path) % locals()842 script_text = ScriptWriter.get_header(script_text) + body843 self.write_script(script_name, _to_bytes(script_text), 'b')844 845 @staticmethod846 def _load_template(dev_path):847 """848 There are a couple of template scripts in the package. This849 function loads one of them and prepares it for use.850 """851 # See https://github.com/pypa/setuptools/issues/134 for info852 # on script file naming and downstream issues with SVR4853 name = 'script.tmpl'854 if dev_path:855 name = name.replace('.tmpl', ' (dev).tmpl')856 857 raw_bytes = resource_string('setuptools', name)858 return raw_bytes.decode('utf-8')859 860 def write_script(self, script_name, contents, mode="t", blockers=()):861 """Write an executable file to the scripts directory"""862 self.delete_blockers( # clean up old .py/.pyw w/o a script863 [os.path.join(self.script_dir, x) for x in blockers]864 )865 log.info("Installing %s script to %s", script_name, self.script_dir)866 target = os.path.join(self.script_dir, script_name)867 self.add_output(target)868 869 if self.dry_run:870 return871 872 mask = current_umask()873 ensure_directory(target)874 if os.path.exists(target):875 os.unlink(target)876 with open(target, "w" + mode) as f: # TODO: is it safe to use utf-8?877 f.write(contents)878 chmod(target, 0o777 - mask)879 880 def install_eggs(self, spec, dist_filename, tmpdir):881 # .egg dirs or files are already built, so just return them882 installer_map = {883 '.egg': self.install_egg,884 '.exe': self.install_exe,885 '.whl': self.install_wheel,886 }887 try:888 install_dist = installer_map[dist_filename.lower()[-4:]]889 except KeyError:890 pass891 else:892 return [install_dist(dist_filename, tmpdir)]893 894 # Anything else, try to extract and build895 setup_base = tmpdir896 if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'):897 unpack_archive(dist_filename, tmpdir, self.unpack_progress)898 elif os.path.isdir(dist_filename):899 setup_base = os.path.abspath(dist_filename)900 901 if (902 setup_base.startswith(tmpdir) # something we downloaded903 and self.build_directory904 and spec is not None905 ):906 setup_base = self.maybe_move(spec, dist_filename, setup_base)907 908 # Find the setup.py file909 setup_script = os.path.join(setup_base, 'setup.py')910 911 if not os.path.exists(setup_script):912 setups = glob(os.path.join(setup_base, '*', 'setup.py'))913 if not setups:914 raise DistutilsError(915 "Couldn't find a setup script in %s"916 % os.path.abspath(dist_filename)917 )918 if len(setups) > 1:919 raise DistutilsError(920 "Multiple setup scripts in %s" % os.path.abspath(dist_filename)921 )922 setup_script = setups[0]923 924 # Now run it, and return the result925 if self.editable:926 log.info(self.report_editable(spec, setup_script))927 return []928 else:929 return self.build_and_install(setup_script, setup_base)930 931 def egg_distribution(self, egg_path):932 if os.path.isdir(egg_path):933 metadata = PathMetadata(egg_path, os.path.join(egg_path, 'EGG-INFO'))934 else:935 metadata = EggMetadata(zipimport.zipimporter(egg_path))936 return Distribution.from_filename(egg_path, metadata=metadata)937 938 # FIXME: 'easy_install.install_egg' is too complex (11)939 def install_egg(self, egg_path, tmpdir): # noqa: C901940 destination = os.path.join(941 self.install_dir,942 os.path.basename(egg_path),943 )944 destination = os.path.abspath(destination)945 if not self.dry_run:946 ensure_directory(destination)947 948 dist = self.egg_distribution(egg_path)949 if not (950 os.path.exists(destination) and os.path.samefile(egg_path, destination)951 ):952 if os.path.isdir(destination) and not os.path.islink(destination):953 dir_util.remove_tree(destination, dry_run=self.dry_run)954 elif os.path.exists(destination):955 self.execute(956 os.unlink,957 (destination,),958 "Removing " + destination,959 )960 try:961 new_dist_is_zipped = False962 if os.path.isdir(egg_path):963 if egg_path.startswith(tmpdir):964 f, m = shutil.move, "Moving"965 else:966 f, m = shutil.copytree, "Copying"967 elif self.should_unzip(dist):968 self.mkpath(destination)969 f, m = self.unpack_and_compile, "Extracting"970 else:971 new_dist_is_zipped = True972 if egg_path.startswith(tmpdir):973 f, m = shutil.move, "Moving"974 else:975 f, m = shutil.copy2, "Copying"976 self.execute(977 f,978 (egg_path, destination),979 (m + " %s to %s")980 % (os.path.basename(egg_path), os.path.dirname(destination)),981 )982 update_dist_caches(983 destination,984 fix_zipimporter_caches=new_dist_is_zipped,985 )986 except Exception:987 update_dist_caches(destination, fix_zipimporter_caches=False)988 raise989 990 self.add_output(destination)991 return self.egg_distribution(destination)992 993 def install_exe(self, dist_filename, tmpdir):994 # See if it's valid, get data995 cfg = extract_wininst_cfg(dist_filename)996 if cfg is None:997 raise DistutilsError(998 "%s is not a valid distutils Windows .exe" % dist_filename999 )1000 # Create a dummy distribution object until we build the real distro1001 dist = Distribution(1002 None,1003 project_name=cfg.get('metadata', 'name'),1004 version=cfg.get('metadata', 'version'),1005 platform=get_platform(),1006 )1007 1008 # Convert the .exe to an unpacked egg1009 egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg')1010 dist.location = egg_path1011 egg_tmp = egg_path + '.tmp'1012 _egg_info = os.path.join(egg_tmp, 'EGG-INFO')1013 pkg_inf = os.path.join(_egg_info, 'PKG-INFO')1014 ensure_directory(pkg_inf) # make sure EGG-INFO dir exists1015 dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX1016 self.exe_to_egg(dist_filename, egg_tmp)1017 1018 # Write EGG-INFO/PKG-INFO1019 if not os.path.exists(pkg_inf):1020 f = open(pkg_inf, 'w') # TODO: probably it is safe to use utf-81021 f.write('Metadata-Version: 1.0\n')1022 for k, v in cfg.items('metadata'):1023 if k != 'target_version':1024 f.write('%s: %s\n' % (k.replace('_', '-').title(), v))1025 f.close()1026 script_dir = os.path.join(_egg_info, 'scripts')1027 # delete entry-point scripts to avoid duping1028 self.delete_blockers([1029 os.path.join(script_dir, args[0]) for args in ScriptWriter.get_args(dist)1030 ])1031 # Build .egg file from tmpdir1032 bdist_egg.make_zipfile(1033 egg_path,1034 egg_tmp,1035 verbose=self.verbose,1036 dry_run=self.dry_run,1037 )1038 # install the .egg1039 return self.install_egg(egg_path, tmpdir)1040 1041 # FIXME: 'easy_install.exe_to_egg' is too complex (12)1042 def exe_to_egg(self, dist_filename, egg_tmp): # noqa: C9011043 """Extract a bdist_wininst to the directories an egg would use"""1044 # Check for .pth file and set up prefix translations1045 prefixes = get_exe_prefixes(dist_filename)1046 to_compile = []1047 native_libs = []1048 top_level = {}1049 1050 def process(src, dst):1051 s = src.lower()1052 for old, new in prefixes:1053 if s.startswith(old):1054 src = new + src[len(old) :]1055 parts = src.split('/')1056 dst = os.path.join(egg_tmp, *parts)1057 dl = dst.lower()1058 if dl.endswith('.pyd') or dl.endswith('.dll'):1059 parts[-1] = bdist_egg.strip_module(parts[-1])1060 top_level[os.path.splitext(parts[0])[0]] = 11061 native_libs.append(src)1062 elif dl.endswith('.py') and old != 'SCRIPTS/':1063 top_level[os.path.splitext(parts[0])[0]] = 11064 to_compile.append(dst)1065 return dst1066 if not src.endswith('.pth'):1067 log.warn("WARNING: can't process %s", src)1068 return None1069 1070 # extract, tracking .pyd/.dll->native_libs and .py -> to_compile1071 unpack_archive(dist_filename, egg_tmp, process)1072 stubs = []1073 for res in native_libs:1074 if res.lower().endswith('.pyd'): # create stubs for .pyd's1075 parts = res.split('/')1076 resource = parts[-1]1077 parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py'1078 pyfile = os.path.join(egg_tmp, *parts)1079 to_compile.append(pyfile)1080 stubs.append(pyfile)1081 bdist_egg.write_stub(resource, pyfile)1082 self.byte_compile(to_compile) # compile .py's1083 bdist_egg.write_safety_flag(1084 os.path.join(egg_tmp, 'EGG-INFO'), bdist_egg.analyze_egg(egg_tmp, stubs)1085 ) # write zip-safety flag1086 1087 for name in 'top_level', 'native_libs':1088 if locals()[name]:1089 txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')1090 if not os.path.exists(txt):1091 f = open(txt, 'w') # TODO: probably it is safe to use utf-81092 f.write('\n'.join(locals()[name]) + '\n')1093 f.close()1094 1095 def install_wheel(self, wheel_path, tmpdir):1096 wheel = Wheel(wheel_path)1097 assert wheel.is_compatible()1098 destination = os.path.join(self.install_dir, wheel.egg_name())1099 destination = os.path.abspath(destination)1100 if not self.dry_run:1101 ensure_directory(destination)1102 if os.path.isdir(destination) and not os.path.islink(destination):1103 dir_util.remove_tree(destination, dry_run=self.dry_run)1104 elif os.path.exists(destination):1105 self.execute(1106 os.unlink,1107 (destination,),1108 "Removing " + destination,1109 )1110 try:1111 self.execute(1112 wheel.install_as_egg,1113 (destination,),1114 ("Installing %s to %s")1115 % (os.path.basename(wheel_path), os.path.dirname(destination)),1116 )1117 finally:1118 update_dist_caches(destination, fix_zipimporter_caches=False)1119 self.add_output(destination)1120 return self.egg_distribution(destination)1121 1122 __mv_warning = textwrap.dedent(1123 """1124 Because this distribution was installed --multi-version, before you can1125 import modules from this package in an application, you will need to1126 'import pkg_resources' and then use a 'require()' call similar to one of1127 these examples, in order to select the desired version:1128 1129 pkg_resources.require("%(name)s") # latest installed version1130 pkg_resources.require("%(name)s==%(version)s") # this exact version1131 pkg_resources.require("%(name)s>=%(version)s") # this version or higher1132 """1133 ).lstrip() # noqa1134 1135 __id_warning = textwrap.dedent(1136 """1137 Note also that the installation directory must be on sys.path at runtime for1138 this to work. (e.g. by being the application's script directory, by being on1139 PYTHONPATH, or by being added to sys.path by your code.)1140 """1141 ) # noqa1142 1143 def installation_report(self, req, dist, what="Installed"):1144 """Helpful installation message for display to package users"""1145 msg = "\n%(what)s %(eggloc)s%(extras)s"1146 if self.multi_version and not self.no_report:1147 msg += '\n' + self.__mv_warning1148 if self.install_dir not in map(normalize_path, sys.path):1149 msg += '\n' + self.__id_warning1150 1151 eggloc = dist.location1152 name = dist.project_name1153 version = dist.version1154 extras = '' # TODO: self.report_extras(req, dist)1155 return msg % locals()1156 1157 __editable_msg = textwrap.dedent(1158 """1159 Extracted editable version of %(spec)s to %(dirname)s1160 1161 If it uses setuptools in its setup script, you can activate it in1162 "development" mode by going to that directory and running::1163 1164 %(python)s setup.py develop1165 1166 See the setuptools documentation for the "develop" command for more info.1167 """1168 ).lstrip() # noqa1169 1170 def report_editable(self, spec, setup_script):1171 dirname = os.path.dirname(setup_script)1172 python = sys.executable1173 return '\n' + self.__editable_msg % locals()1174 1175 def run_setup(self, setup_script, setup_base, args):1176 sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)1177 sys.modules.setdefault('distutils.command.egg_info', egg_info)1178 1179 args = list(args)1180 if self.verbose > 2:1181 v = 'v' * (self.verbose - 1)1182 args.insert(0, '-' + v)1183 elif self.verbose < 2:1184 args.insert(0, '-q')1185 if self.dry_run:1186 args.insert(0, '-n')1187 log.info("Running %s %s", setup_script[len(setup_base) + 1 :], ' '.join(args))1188 try:1189 run_setup(setup_script, args)1190 except SystemExit as v:1191 raise DistutilsError("Setup script exited with %s" % (v.args[0],)) from v1192 1193 def build_and_install(self, setup_script, setup_base):1194 args = ['bdist_egg', '--dist-dir']1195 1196 dist_dir = tempfile.mkdtemp(1197 prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script)1198 )1199 try:1200 self._set_fetcher_options(os.path.dirname(setup_script))