]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
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 | """ |