Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Side by Side Diff: third_party/google-endpoints/setuptools/command/easy_install.py

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

Powered by Google App Engine
This is Rietveld 408576698