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