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