blob: 5e0f97cfea5484ea1ea139e0f4b8e8553b80b00d [file] [log] [blame]
Yi Kong878f9942023-12-13 12:55:04 +09001"""
2Easy Install
3------------
4
5A tool for doing automatic download/extract/build of distutils-based Python
6packages. For detailed documentation, see the accompanying EasyInstall.txt
7file, or visit the `EasyInstall home page`__.
8
9__ https://setuptools.readthedocs.io/en/latest/deprecated/easy_install.html
10
11"""
12
13from glob import glob
14from distutils.util import get_platform
15from distutils.util import convert_path, subst_vars
16from distutils.errors import (
17 DistutilsArgError, DistutilsOptionError,
18 DistutilsError, DistutilsPlatformError,
19)
20from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS
21from distutils import log, dir_util
22from distutils.command.build_scripts import first_line_re
23from distutils.spawn import find_executable
24import sys
25import os
26import zipimport
27import shutil
28import tempfile
29import zipfile
30import re
31import stat
32import random
33import textwrap
34import warnings
35import site
36import struct
37import contextlib
38import subprocess
39import shlex
40import io
41import configparser
42
43
44from sysconfig import get_config_vars, get_path
45
46from setuptools import SetuptoolsDeprecationWarning
47
48from setuptools import Command
49from setuptools.sandbox import run_setup
50from setuptools.command import setopt
51from setuptools.archive_util import unpack_archive
52from setuptools.package_index import (
53 PackageIndex, parse_requirement_arg, URL_SCHEME,
54)
55from setuptools.command import bdist_egg, egg_info
56from setuptools.wheel import Wheel
57from 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)
63import pkg_resources
64
65# Turn on PEP440Warnings
66warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
67
68__all__ = [
69 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
70 'get_exe_prefixes',
71]
72
73
74def is_64bit():
75 return struct.calcsize("P") == 8
76
77
78def 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
94def _to_bytes(s):
95 return s.encode('utf8')
96
97
98def isascii(s):
99 try:
100 s.encode('ascii')
101 return True
102 except UnicodeError:
103 return False
104
105
106def _one_liner(text):
107 return textwrap.dedent(text).strip().replace('\n', '; ')
108
109
110class 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
1360def _pythonpath():
1361 items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
1362 return filter(None, items)
1363
1364
1365def 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
1435def 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
1481def 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
1522def 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
1559class 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
1667class 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
1689if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite':
1690 PthDistributions = RewritePthDistributions
1691
1692
1693def _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
1705def 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
1714def 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
1796def _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
1816def _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
1848def _uncache(normalized_path, cache):
1849 _update_zipimporter_cache(normalized_path, cache)
1850
1851
1852def _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.
1869if '__pypy__' in sys.builtin_module_names:
1870 _replace_zip_directory_cache_data = \
1871 _remove_and_clear_zip_directory_cache_data
1872else:
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
1893def 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
1903def 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
1913def nt_quote_arg(arg):
1914 """Quote a command line argument according to Windows parsing rules"""
1915 return subprocess.list2cmdline([arg])
1916
1917
1918def 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
1932try:
1933 from os import chmod as _chmod
1934except ImportError:
1935 # Jython compatibility
1936 def _chmod(*args):
1937 pass
1938
1939
1940def 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
1948class 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.
2032sys_executable = CommandSpec._sys_executable()
2033
2034
2035class WindowsCommandSpec(CommandSpec):
2036 split_args = dict(posix=False)
2037
2038
2039class 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
2155class 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
2220class 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
2252get_script_args = ScriptWriter.get_script_args
2253get_script_header = ScriptWriter.get_script_header
2254
2255
2256def 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
2272def load_launcher_manifest(name):
2273 manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml')
2274 return manifest.decode('utf-8') % vars()
2275
2276
2277def rmtree(path, ignore_errors=False, onerror=auto_chmod):
2278 return shutil.rmtree(path, ignore_errors, onerror)
2279
2280
2281def current_umask():
2282 tmp = os.umask(0o022)
2283 os.umask(tmp)
2284 return tmp
2285
2286
2287class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
2288 """
2289 Warning for EasyInstall deprecations, bypassing suppression.
2290 """