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