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