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