Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/pip/_internal/utils/misc.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:32:28 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:d30785e31577 | 1:56ad4e20f292 |
---|---|
1 from __future__ import absolute_import | |
2 | |
3 import contextlib | |
4 import errno | |
5 import getpass | |
6 import io | |
7 # we have a submodule named 'logging' which would shadow this if we used the | |
8 # regular name: | |
9 import logging as std_logging | |
10 import os | |
11 import posixpath | |
12 import re | |
13 import shutil | |
14 import stat | |
15 import subprocess | |
16 import sys | |
17 import tarfile | |
18 import zipfile | |
19 from collections import deque | |
20 | |
21 from pip._vendor import pkg_resources | |
22 # NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is | |
23 # why we ignore the type on this import. | |
24 from pip._vendor.retrying import retry # type: ignore | |
25 from pip._vendor.six import PY2, text_type | |
26 from pip._vendor.six.moves import input, shlex_quote | |
27 from pip._vendor.six.moves.urllib import parse as urllib_parse | |
28 from pip._vendor.six.moves.urllib import request as urllib_request | |
29 from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote | |
30 | |
31 from pip import __version__ | |
32 from pip._internal.exceptions import CommandError, InstallationError | |
33 from pip._internal.locations import site_packages, user_site | |
34 from pip._internal.utils.compat import ( | |
35 WINDOWS, console_to_str, expanduser, stdlib_pkgs, str_to_display, | |
36 ) | |
37 from pip._internal.utils.marker_files import write_delete_marker_file | |
38 from pip._internal.utils.typing import MYPY_CHECK_RUNNING | |
39 from pip._internal.utils.virtualenv import ( | |
40 running_under_virtualenv, virtualenv_no_global, | |
41 ) | |
42 | |
43 if PY2: | |
44 from io import BytesIO as StringIO | |
45 else: | |
46 from io import StringIO | |
47 | |
48 if MYPY_CHECK_RUNNING: | |
49 from typing import ( | |
50 Any, AnyStr, Container, Iterable, List, Mapping, Match, Optional, Text, | |
51 Tuple, Union, cast, | |
52 ) | |
53 from pip._vendor.pkg_resources import Distribution | |
54 from pip._internal.models.link import Link | |
55 from pip._internal.utils.ui import SpinnerInterface | |
56 | |
57 VersionInfo = Tuple[int, int, int] | |
58 else: | |
59 # typing's cast() is needed at runtime, but we don't want to import typing. | |
60 # Thus, we use a dummy no-op version, which we tell mypy to ignore. | |
61 def cast(type_, value): # type: ignore | |
62 return value | |
63 | |
64 | |
65 __all__ = ['rmtree', 'display_path', 'backup_dir', | |
66 'ask', 'splitext', | |
67 'format_size', 'is_installable_dir', | |
68 'is_svn_page', 'file_contents', | |
69 'split_leading_dir', 'has_leading_dir', | |
70 'normalize_path', | |
71 'renames', 'get_prog', | |
72 'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess', | |
73 'captured_stdout', 'ensure_dir', | |
74 'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS', 'WHEEL_EXTENSION', | |
75 'get_installed_version', 'remove_auth_from_url'] | |
76 | |
77 | |
78 logger = std_logging.getLogger(__name__) | |
79 subprocess_logger = std_logging.getLogger('pip.subprocessor') | |
80 | |
81 LOG_DIVIDER = '----------------------------------------' | |
82 | |
83 WHEEL_EXTENSION = '.whl' | |
84 BZ2_EXTENSIONS = ('.tar.bz2', '.tbz') | |
85 XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma') | |
86 ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION) | |
87 TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar') | |
88 ARCHIVE_EXTENSIONS = ( | |
89 ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS) | |
90 SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS | |
91 | |
92 try: | |
93 import bz2 # noqa | |
94 SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS | |
95 except ImportError: | |
96 logger.debug('bz2 module is not available') | |
97 | |
98 try: | |
99 # Only for Python 3.3+ | |
100 import lzma # noqa | |
101 SUPPORTED_EXTENSIONS += XZ_EXTENSIONS | |
102 except ImportError: | |
103 logger.debug('lzma module is not available') | |
104 | |
105 | |
106 def get_pip_version(): | |
107 # type: () -> str | |
108 pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") | |
109 pip_pkg_dir = os.path.abspath(pip_pkg_dir) | |
110 | |
111 return ( | |
112 'pip {} from {} (python {})'.format( | |
113 __version__, pip_pkg_dir, sys.version[:3], | |
114 ) | |
115 ) | |
116 | |
117 | |
118 def normalize_version_info(py_version_info): | |
119 # type: (Tuple[int, ...]) -> Tuple[int, int, int] | |
120 """ | |
121 Convert a tuple of ints representing a Python version to one of length | |
122 three. | |
123 | |
124 :param py_version_info: a tuple of ints representing a Python version, | |
125 or None to specify no version. The tuple can have any length. | |
126 | |
127 :return: a tuple of length three if `py_version_info` is non-None. | |
128 Otherwise, return `py_version_info` unchanged (i.e. None). | |
129 """ | |
130 if len(py_version_info) < 3: | |
131 py_version_info += (3 - len(py_version_info)) * (0,) | |
132 elif len(py_version_info) > 3: | |
133 py_version_info = py_version_info[:3] | |
134 | |
135 return cast('VersionInfo', py_version_info) | |
136 | |
137 | |
138 def ensure_dir(path): | |
139 # type: (AnyStr) -> None | |
140 """os.path.makedirs without EEXIST.""" | |
141 try: | |
142 os.makedirs(path) | |
143 except OSError as e: | |
144 if e.errno != errno.EEXIST: | |
145 raise | |
146 | |
147 | |
148 def get_prog(): | |
149 # type: () -> str | |
150 try: | |
151 prog = os.path.basename(sys.argv[0]) | |
152 if prog in ('__main__.py', '-c'): | |
153 return "%s -m pip" % sys.executable | |
154 else: | |
155 return prog | |
156 except (AttributeError, TypeError, IndexError): | |
157 pass | |
158 return 'pip' | |
159 | |
160 | |
161 # Retry every half second for up to 3 seconds | |
162 @retry(stop_max_delay=3000, wait_fixed=500) | |
163 def rmtree(dir, ignore_errors=False): | |
164 # type: (str, bool) -> None | |
165 shutil.rmtree(dir, ignore_errors=ignore_errors, | |
166 onerror=rmtree_errorhandler) | |
167 | |
168 | |
169 def rmtree_errorhandler(func, path, exc_info): | |
170 """On Windows, the files in .svn are read-only, so when rmtree() tries to | |
171 remove them, an exception is thrown. We catch that here, remove the | |
172 read-only attribute, and hopefully continue without problems.""" | |
173 # if file type currently read only | |
174 if os.stat(path).st_mode & stat.S_IREAD: | |
175 # convert to read/write | |
176 os.chmod(path, stat.S_IWRITE) | |
177 # use the original function to repeat the operation | |
178 func(path) | |
179 return | |
180 else: | |
181 raise | |
182 | |
183 | |
184 def path_to_display(path): | |
185 # type: (Optional[Union[str, Text]]) -> Optional[Text] | |
186 """ | |
187 Convert a bytes (or text) path to text (unicode in Python 2) for display | |
188 and logging purposes. | |
189 | |
190 This function should never error out. Also, this function is mainly needed | |
191 for Python 2 since in Python 3 str paths are already text. | |
192 """ | |
193 if path is None: | |
194 return None | |
195 if isinstance(path, text_type): | |
196 return path | |
197 # Otherwise, path is a bytes object (str in Python 2). | |
198 try: | |
199 display_path = path.decode(sys.getfilesystemencoding(), 'strict') | |
200 except UnicodeDecodeError: | |
201 # Include the full bytes to make troubleshooting easier, even though | |
202 # it may not be very human readable. | |
203 if PY2: | |
204 # Convert the bytes to a readable str representation using | |
205 # repr(), and then convert the str to unicode. | |
206 # Also, we add the prefix "b" to the repr() return value both | |
207 # to make the Python 2 output look like the Python 3 output, and | |
208 # to signal to the user that this is a bytes representation. | |
209 display_path = str_to_display('b{!r}'.format(path)) | |
210 else: | |
211 # Silence the "F821 undefined name 'ascii'" flake8 error since | |
212 # in Python 3 ascii() is a built-in. | |
213 display_path = ascii(path) # noqa: F821 | |
214 | |
215 return display_path | |
216 | |
217 | |
218 def display_path(path): | |
219 # type: (Union[str, Text]) -> str | |
220 """Gives the display value for a given path, making it relative to cwd | |
221 if possible.""" | |
222 path = os.path.normcase(os.path.abspath(path)) | |
223 if sys.version_info[0] == 2: | |
224 path = path.decode(sys.getfilesystemencoding(), 'replace') | |
225 path = path.encode(sys.getdefaultencoding(), 'replace') | |
226 if path.startswith(os.getcwd() + os.path.sep): | |
227 path = '.' + path[len(os.getcwd()):] | |
228 return path | |
229 | |
230 | |
231 def backup_dir(dir, ext='.bak'): | |
232 # type: (str, str) -> str | |
233 """Figure out the name of a directory to back up the given dir to | |
234 (adding .bak, .bak2, etc)""" | |
235 n = 1 | |
236 extension = ext | |
237 while os.path.exists(dir + extension): | |
238 n += 1 | |
239 extension = ext + str(n) | |
240 return dir + extension | |
241 | |
242 | |
243 def ask_path_exists(message, options): | |
244 # type: (str, Iterable[str]) -> str | |
245 for action in os.environ.get('PIP_EXISTS_ACTION', '').split(): | |
246 if action in options: | |
247 return action | |
248 return ask(message, options) | |
249 | |
250 | |
251 def _check_no_input(message): | |
252 # type: (str) -> None | |
253 """Raise an error if no input is allowed.""" | |
254 if os.environ.get('PIP_NO_INPUT'): | |
255 raise Exception( | |
256 'No input was expected ($PIP_NO_INPUT set); question: %s' % | |
257 message | |
258 ) | |
259 | |
260 | |
261 def ask(message, options): | |
262 # type: (str, Iterable[str]) -> str | |
263 """Ask the message interactively, with the given possible responses""" | |
264 while 1: | |
265 _check_no_input(message) | |
266 response = input(message) | |
267 response = response.strip().lower() | |
268 if response not in options: | |
269 print( | |
270 'Your response (%r) was not one of the expected responses: ' | |
271 '%s' % (response, ', '.join(options)) | |
272 ) | |
273 else: | |
274 return response | |
275 | |
276 | |
277 def ask_input(message): | |
278 # type: (str) -> str | |
279 """Ask for input interactively.""" | |
280 _check_no_input(message) | |
281 return input(message) | |
282 | |
283 | |
284 def ask_password(message): | |
285 # type: (str) -> str | |
286 """Ask for a password interactively.""" | |
287 _check_no_input(message) | |
288 return getpass.getpass(message) | |
289 | |
290 | |
291 def format_size(bytes): | |
292 # type: (float) -> str | |
293 if bytes > 1000 * 1000: | |
294 return '%.1fMB' % (bytes / 1000.0 / 1000) | |
295 elif bytes > 10 * 1000: | |
296 return '%ikB' % (bytes / 1000) | |
297 elif bytes > 1000: | |
298 return '%.1fkB' % (bytes / 1000.0) | |
299 else: | |
300 return '%ibytes' % bytes | |
301 | |
302 | |
303 def is_installable_dir(path): | |
304 # type: (str) -> bool | |
305 """Is path is a directory containing setup.py or pyproject.toml? | |
306 """ | |
307 if not os.path.isdir(path): | |
308 return False | |
309 setup_py = os.path.join(path, 'setup.py') | |
310 if os.path.isfile(setup_py): | |
311 return True | |
312 pyproject_toml = os.path.join(path, 'pyproject.toml') | |
313 if os.path.isfile(pyproject_toml): | |
314 return True | |
315 return False | |
316 | |
317 | |
318 def is_svn_page(html): | |
319 # type: (Union[str, Text]) -> Optional[Match[Union[str, Text]]] | |
320 """ | |
321 Returns true if the page appears to be the index page of an svn repository | |
322 """ | |
323 return (re.search(r'<title>[^<]*Revision \d+:', html) and | |
324 re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I)) | |
325 | |
326 | |
327 def file_contents(filename): | |
328 # type: (str) -> Text | |
329 with open(filename, 'rb') as fp: | |
330 return fp.read().decode('utf-8') | |
331 | |
332 | |
333 def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): | |
334 """Yield pieces of data from a file-like object until EOF.""" | |
335 while True: | |
336 chunk = file.read(size) | |
337 if not chunk: | |
338 break | |
339 yield chunk | |
340 | |
341 | |
342 def split_leading_dir(path): | |
343 # type: (Union[str, Text]) -> List[Union[str, Text]] | |
344 path = path.lstrip('/').lstrip('\\') | |
345 if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or | |
346 '\\' not in path): | |
347 return path.split('/', 1) | |
348 elif '\\' in path: | |
349 return path.split('\\', 1) | |
350 else: | |
351 return [path, ''] | |
352 | |
353 | |
354 def has_leading_dir(paths): | |
355 # type: (Iterable[Union[str, Text]]) -> bool | |
356 """Returns true if all the paths have the same leading path name | |
357 (i.e., everything is in one subdirectory in an archive)""" | |
358 common_prefix = None | |
359 for path in paths: | |
360 prefix, rest = split_leading_dir(path) | |
361 if not prefix: | |
362 return False | |
363 elif common_prefix is None: | |
364 common_prefix = prefix | |
365 elif prefix != common_prefix: | |
366 return False | |
367 return True | |
368 | |
369 | |
370 def normalize_path(path, resolve_symlinks=True): | |
371 # type: (str, bool) -> str | |
372 """ | |
373 Convert a path to its canonical, case-normalized, absolute version. | |
374 | |
375 """ | |
376 path = expanduser(path) | |
377 if resolve_symlinks: | |
378 path = os.path.realpath(path) | |
379 else: | |
380 path = os.path.abspath(path) | |
381 return os.path.normcase(path) | |
382 | |
383 | |
384 def splitext(path): | |
385 # type: (str) -> Tuple[str, str] | |
386 """Like os.path.splitext, but take off .tar too""" | |
387 base, ext = posixpath.splitext(path) | |
388 if base.lower().endswith('.tar'): | |
389 ext = base[-4:] + ext | |
390 base = base[:-4] | |
391 return base, ext | |
392 | |
393 | |
394 def renames(old, new): | |
395 # type: (str, str) -> None | |
396 """Like os.renames(), but handles renaming across devices.""" | |
397 # Implementation borrowed from os.renames(). | |
398 head, tail = os.path.split(new) | |
399 if head and tail and not os.path.exists(head): | |
400 os.makedirs(head) | |
401 | |
402 shutil.move(old, new) | |
403 | |
404 head, tail = os.path.split(old) | |
405 if head and tail: | |
406 try: | |
407 os.removedirs(head) | |
408 except OSError: | |
409 pass | |
410 | |
411 | |
412 def is_local(path): | |
413 # type: (str) -> bool | |
414 """ | |
415 Return True if path is within sys.prefix, if we're running in a virtualenv. | |
416 | |
417 If we're not in a virtualenv, all paths are considered "local." | |
418 | |
419 """ | |
420 if not running_under_virtualenv(): | |
421 return True | |
422 return normalize_path(path).startswith(normalize_path(sys.prefix)) | |
423 | |
424 | |
425 def dist_is_local(dist): | |
426 # type: (Distribution) -> bool | |
427 """ | |
428 Return True if given Distribution object is installed locally | |
429 (i.e. within current virtualenv). | |
430 | |
431 Always True if we're not in a virtualenv. | |
432 | |
433 """ | |
434 return is_local(dist_location(dist)) | |
435 | |
436 | |
437 def dist_in_usersite(dist): | |
438 # type: (Distribution) -> bool | |
439 """ | |
440 Return True if given Distribution is installed in user site. | |
441 """ | |
442 norm_path = normalize_path(dist_location(dist)) | |
443 return norm_path.startswith(normalize_path(user_site)) | |
444 | |
445 | |
446 def dist_in_site_packages(dist): | |
447 # type: (Distribution) -> bool | |
448 """ | |
449 Return True if given Distribution is installed in | |
450 sysconfig.get_python_lib(). | |
451 """ | |
452 return normalize_path( | |
453 dist_location(dist) | |
454 ).startswith(normalize_path(site_packages)) | |
455 | |
456 | |
457 def dist_is_editable(dist): | |
458 # type: (Distribution) -> bool | |
459 """ | |
460 Return True if given Distribution is an editable install. | |
461 """ | |
462 for path_item in sys.path: | |
463 egg_link = os.path.join(path_item, dist.project_name + '.egg-link') | |
464 if os.path.isfile(egg_link): | |
465 return True | |
466 return False | |
467 | |
468 | |
469 def get_installed_distributions( | |
470 local_only=True, # type: bool | |
471 skip=stdlib_pkgs, # type: Container[str] | |
472 include_editables=True, # type: bool | |
473 editables_only=False, # type: bool | |
474 user_only=False, # type: bool | |
475 paths=None # type: Optional[List[str]] | |
476 ): | |
477 # type: (...) -> List[Distribution] | |
478 """ | |
479 Return a list of installed Distribution objects. | |
480 | |
481 If ``local_only`` is True (default), only return installations | |
482 local to the current virtualenv, if in a virtualenv. | |
483 | |
484 ``skip`` argument is an iterable of lower-case project names to | |
485 ignore; defaults to stdlib_pkgs | |
486 | |
487 If ``include_editables`` is False, don't report editables. | |
488 | |
489 If ``editables_only`` is True , only report editables. | |
490 | |
491 If ``user_only`` is True , only report installations in the user | |
492 site directory. | |
493 | |
494 If ``paths`` is set, only report the distributions present at the | |
495 specified list of locations. | |
496 """ | |
497 if paths: | |
498 working_set = pkg_resources.WorkingSet(paths) | |
499 else: | |
500 working_set = pkg_resources.working_set | |
501 | |
502 if local_only: | |
503 local_test = dist_is_local | |
504 else: | |
505 def local_test(d): | |
506 return True | |
507 | |
508 if include_editables: | |
509 def editable_test(d): | |
510 return True | |
511 else: | |
512 def editable_test(d): | |
513 return not dist_is_editable(d) | |
514 | |
515 if editables_only: | |
516 def editables_only_test(d): | |
517 return dist_is_editable(d) | |
518 else: | |
519 def editables_only_test(d): | |
520 return True | |
521 | |
522 if user_only: | |
523 user_test = dist_in_usersite | |
524 else: | |
525 def user_test(d): | |
526 return True | |
527 | |
528 # because of pkg_resources vendoring, mypy cannot find stub in typeshed | |
529 return [d for d in working_set # type: ignore | |
530 if local_test(d) and | |
531 d.key not in skip and | |
532 editable_test(d) and | |
533 editables_only_test(d) and | |
534 user_test(d) | |
535 ] | |
536 | |
537 | |
538 def egg_link_path(dist): | |
539 # type: (Distribution) -> Optional[str] | |
540 """ | |
541 Return the path for the .egg-link file if it exists, otherwise, None. | |
542 | |
543 There's 3 scenarios: | |
544 1) not in a virtualenv | |
545 try to find in site.USER_SITE, then site_packages | |
546 2) in a no-global virtualenv | |
547 try to find in site_packages | |
548 3) in a yes-global virtualenv | |
549 try to find in site_packages, then site.USER_SITE | |
550 (don't look in global location) | |
551 | |
552 For #1 and #3, there could be odd cases, where there's an egg-link in 2 | |
553 locations. | |
554 | |
555 This method will just return the first one found. | |
556 """ | |
557 sites = [] | |
558 if running_under_virtualenv(): | |
559 if virtualenv_no_global(): | |
560 sites.append(site_packages) | |
561 else: | |
562 sites.append(site_packages) | |
563 if user_site: | |
564 sites.append(user_site) | |
565 else: | |
566 if user_site: | |
567 sites.append(user_site) | |
568 sites.append(site_packages) | |
569 | |
570 for site in sites: | |
571 egglink = os.path.join(site, dist.project_name) + '.egg-link' | |
572 if os.path.isfile(egglink): | |
573 return egglink | |
574 return None | |
575 | |
576 | |
577 def dist_location(dist): | |
578 # type: (Distribution) -> str | |
579 """ | |
580 Get the site-packages location of this distribution. Generally | |
581 this is dist.location, except in the case of develop-installed | |
582 packages, where dist.location is the source code location, and we | |
583 want to know where the egg-link file is. | |
584 | |
585 """ | |
586 egg_link = egg_link_path(dist) | |
587 if egg_link: | |
588 return egg_link | |
589 return dist.location | |
590 | |
591 | |
592 def current_umask(): | |
593 """Get the current umask which involves having to set it temporarily.""" | |
594 mask = os.umask(0) | |
595 os.umask(mask) | |
596 return mask | |
597 | |
598 | |
599 def unzip_file(filename, location, flatten=True): | |
600 # type: (str, str, bool) -> None | |
601 """ | |
602 Unzip the file (with path `filename`) to the destination `location`. All | |
603 files are written based on system defaults and umask (i.e. permissions are | |
604 not preserved), except that regular file members with any execute | |
605 permissions (user, group, or world) have "chmod +x" applied after being | |
606 written. Note that for windows, any execute changes using os.chmod are | |
607 no-ops per the python docs. | |
608 """ | |
609 ensure_dir(location) | |
610 zipfp = open(filename, 'rb') | |
611 try: | |
612 zip = zipfile.ZipFile(zipfp, allowZip64=True) | |
613 leading = has_leading_dir(zip.namelist()) and flatten | |
614 for info in zip.infolist(): | |
615 name = info.filename | |
616 fn = name | |
617 if leading: | |
618 fn = split_leading_dir(name)[1] | |
619 fn = os.path.join(location, fn) | |
620 dir = os.path.dirname(fn) | |
621 if fn.endswith('/') or fn.endswith('\\'): | |
622 # A directory | |
623 ensure_dir(fn) | |
624 else: | |
625 ensure_dir(dir) | |
626 # Don't use read() to avoid allocating an arbitrarily large | |
627 # chunk of memory for the file's content | |
628 fp = zip.open(name) | |
629 try: | |
630 with open(fn, 'wb') as destfp: | |
631 shutil.copyfileobj(fp, destfp) | |
632 finally: | |
633 fp.close() | |
634 mode = info.external_attr >> 16 | |
635 # if mode and regular file and any execute permissions for | |
636 # user/group/world? | |
637 if mode and stat.S_ISREG(mode) and mode & 0o111: | |
638 # make dest file have execute for user/group/world | |
639 # (chmod +x) no-op on windows per python docs | |
640 os.chmod(fn, (0o777 - current_umask() | 0o111)) | |
641 finally: | |
642 zipfp.close() | |
643 | |
644 | |
645 def untar_file(filename, location): | |
646 # type: (str, str) -> None | |
647 """ | |
648 Untar the file (with path `filename`) to the destination `location`. | |
649 All files are written based on system defaults and umask (i.e. permissions | |
650 are not preserved), except that regular file members with any execute | |
651 permissions (user, group, or world) have "chmod +x" applied after being | |
652 written. Note that for windows, any execute changes using os.chmod are | |
653 no-ops per the python docs. | |
654 """ | |
655 ensure_dir(location) | |
656 if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'): | |
657 mode = 'r:gz' | |
658 elif filename.lower().endswith(BZ2_EXTENSIONS): | |
659 mode = 'r:bz2' | |
660 elif filename.lower().endswith(XZ_EXTENSIONS): | |
661 mode = 'r:xz' | |
662 elif filename.lower().endswith('.tar'): | |
663 mode = 'r' | |
664 else: | |
665 logger.warning( | |
666 'Cannot determine compression type for file %s', filename, | |
667 ) | |
668 mode = 'r:*' | |
669 tar = tarfile.open(filename, mode) | |
670 try: | |
671 leading = has_leading_dir([ | |
672 member.name for member in tar.getmembers() | |
673 ]) | |
674 for member in tar.getmembers(): | |
675 fn = member.name | |
676 if leading: | |
677 # https://github.com/python/mypy/issues/1174 | |
678 fn = split_leading_dir(fn)[1] # type: ignore | |
679 path = os.path.join(location, fn) | |
680 if member.isdir(): | |
681 ensure_dir(path) | |
682 elif member.issym(): | |
683 try: | |
684 # https://github.com/python/typeshed/issues/2673 | |
685 tar._extract_member(member, path) # type: ignore | |
686 except Exception as exc: | |
687 # Some corrupt tar files seem to produce this | |
688 # (specifically bad symlinks) | |
689 logger.warning( | |
690 'In the tar file %s the member %s is invalid: %s', | |
691 filename, member.name, exc, | |
692 ) | |
693 continue | |
694 else: | |
695 try: | |
696 fp = tar.extractfile(member) | |
697 except (KeyError, AttributeError) as exc: | |
698 # Some corrupt tar files seem to produce this | |
699 # (specifically bad symlinks) | |
700 logger.warning( | |
701 'In the tar file %s the member %s is invalid: %s', | |
702 filename, member.name, exc, | |
703 ) | |
704 continue | |
705 ensure_dir(os.path.dirname(path)) | |
706 with open(path, 'wb') as destfp: | |
707 shutil.copyfileobj(fp, destfp) | |
708 fp.close() | |
709 # Update the timestamp (useful for cython compiled files) | |
710 # https://github.com/python/typeshed/issues/2673 | |
711 tar.utime(member, path) # type: ignore | |
712 # member have any execute permissions for user/group/world? | |
713 if member.mode & 0o111: | |
714 # make dest file have execute for user/group/world | |
715 # no-op on windows per python docs | |
716 os.chmod(path, (0o777 - current_umask() | 0o111)) | |
717 finally: | |
718 tar.close() | |
719 | |
720 | |
721 def unpack_file( | |
722 filename, # type: str | |
723 location, # type: str | |
724 content_type, # type: Optional[str] | |
725 link # type: Optional[Link] | |
726 ): | |
727 # type: (...) -> None | |
728 filename = os.path.realpath(filename) | |
729 if (content_type == 'application/zip' or | |
730 filename.lower().endswith(ZIP_EXTENSIONS) or | |
731 zipfile.is_zipfile(filename)): | |
732 unzip_file( | |
733 filename, | |
734 location, | |
735 flatten=not filename.endswith('.whl') | |
736 ) | |
737 elif (content_type == 'application/x-gzip' or | |
738 tarfile.is_tarfile(filename) or | |
739 filename.lower().endswith( | |
740 TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)): | |
741 untar_file(filename, location) | |
742 elif (content_type and content_type.startswith('text/html') and | |
743 is_svn_page(file_contents(filename))): | |
744 # We don't really care about this | |
745 from pip._internal.vcs.subversion import Subversion | |
746 url = 'svn+' + link.url | |
747 Subversion().unpack(location, url=url) | |
748 else: | |
749 # FIXME: handle? | |
750 # FIXME: magic signatures? | |
751 logger.critical( | |
752 'Cannot unpack file %s (downloaded from %s, content-type: %s); ' | |
753 'cannot detect archive format', | |
754 filename, location, content_type, | |
755 ) | |
756 raise InstallationError( | |
757 'Cannot determine archive format of %s' % location | |
758 ) | |
759 | |
760 | |
761 def format_command_args(args): | |
762 # type: (List[str]) -> str | |
763 """ | |
764 Format command arguments for display. | |
765 """ | |
766 return ' '.join(shlex_quote(arg) for arg in args) | |
767 | |
768 | |
769 def make_subprocess_output_error( | |
770 cmd_args, # type: List[str] | |
771 cwd, # type: Optional[str] | |
772 lines, # type: List[Text] | |
773 exit_status, # type: int | |
774 ): | |
775 # type: (...) -> Text | |
776 """ | |
777 Create and return the error message to use to log a subprocess error | |
778 with command output. | |
779 | |
780 :param lines: A list of lines, each ending with a newline. | |
781 """ | |
782 command = format_command_args(cmd_args) | |
783 # Convert `command` and `cwd` to text (unicode in Python 2) so we can use | |
784 # them as arguments in the unicode format string below. This avoids | |
785 # "UnicodeDecodeError: 'ascii' codec can't decode byte ..." in Python 2 | |
786 # if either contains a non-ascii character. | |
787 command_display = str_to_display(command, desc='command bytes') | |
788 cwd_display = path_to_display(cwd) | |
789 | |
790 # We know the joined output value ends in a newline. | |
791 output = ''.join(lines) | |
792 msg = ( | |
793 # Use a unicode string to avoid "UnicodeEncodeError: 'ascii' | |
794 # codec can't encode character ..." in Python 2 when a format | |
795 # argument (e.g. `output`) has a non-ascii character. | |
796 u'Command errored out with exit status {exit_status}:\n' | |
797 ' command: {command_display}\n' | |
798 ' cwd: {cwd_display}\n' | |
799 'Complete output ({line_count} lines):\n{output}{divider}' | |
800 ).format( | |
801 exit_status=exit_status, | |
802 command_display=command_display, | |
803 cwd_display=cwd_display, | |
804 line_count=len(lines), | |
805 output=output, | |
806 divider=LOG_DIVIDER, | |
807 ) | |
808 return msg | |
809 | |
810 | |
811 def call_subprocess( | |
812 cmd, # type: List[str] | |
813 show_stdout=False, # type: bool | |
814 cwd=None, # type: Optional[str] | |
815 on_returncode='raise', # type: str | |
816 extra_ok_returncodes=None, # type: Optional[Iterable[int]] | |
817 command_desc=None, # type: Optional[str] | |
818 extra_environ=None, # type: Optional[Mapping[str, Any]] | |
819 unset_environ=None, # type: Optional[Iterable[str]] | |
820 spinner=None # type: Optional[SpinnerInterface] | |
821 ): | |
822 # type: (...) -> Text | |
823 """ | |
824 Args: | |
825 show_stdout: if true, use INFO to log the subprocess's stderr and | |
826 stdout streams. Otherwise, use DEBUG. Defaults to False. | |
827 extra_ok_returncodes: an iterable of integer return codes that are | |
828 acceptable, in addition to 0. Defaults to None, which means []. | |
829 unset_environ: an iterable of environment variable names to unset | |
830 prior to calling subprocess.Popen(). | |
831 """ | |
832 if extra_ok_returncodes is None: | |
833 extra_ok_returncodes = [] | |
834 if unset_environ is None: | |
835 unset_environ = [] | |
836 # Most places in pip use show_stdout=False. What this means is-- | |
837 # | |
838 # - We connect the child's output (combined stderr and stdout) to a | |
839 # single pipe, which we read. | |
840 # - We log this output to stderr at DEBUG level as it is received. | |
841 # - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't | |
842 # requested), then we show a spinner so the user can still see the | |
843 # subprocess is in progress. | |
844 # - If the subprocess exits with an error, we log the output to stderr | |
845 # at ERROR level if it hasn't already been displayed to the console | |
846 # (e.g. if --verbose logging wasn't enabled). This way we don't log | |
847 # the output to the console twice. | |
848 # | |
849 # If show_stdout=True, then the above is still done, but with DEBUG | |
850 # replaced by INFO. | |
851 if show_stdout: | |
852 # Then log the subprocess output at INFO level. | |
853 log_subprocess = subprocess_logger.info | |
854 used_level = std_logging.INFO | |
855 else: | |
856 # Then log the subprocess output using DEBUG. This also ensures | |
857 # it will be logged to the log file (aka user_log), if enabled. | |
858 log_subprocess = subprocess_logger.debug | |
859 used_level = std_logging.DEBUG | |
860 | |
861 # Whether the subprocess will be visible in the console. | |
862 showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level | |
863 | |
864 # Only use the spinner if we're not showing the subprocess output | |
865 # and we have a spinner. | |
866 use_spinner = not showing_subprocess and spinner is not None | |
867 | |
868 if command_desc is None: | |
869 command_desc = format_command_args(cmd) | |
870 | |
871 log_subprocess("Running command %s", command_desc) | |
872 env = os.environ.copy() | |
873 if extra_environ: | |
874 env.update(extra_environ) | |
875 for name in unset_environ: | |
876 env.pop(name, None) | |
877 try: | |
878 proc = subprocess.Popen( | |
879 cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, | |
880 stdout=subprocess.PIPE, cwd=cwd, env=env, | |
881 ) | |
882 proc.stdin.close() | |
883 except Exception as exc: | |
884 subprocess_logger.critical( | |
885 "Error %s while executing command %s", exc, command_desc, | |
886 ) | |
887 raise | |
888 all_output = [] | |
889 while True: | |
890 # The "line" value is a unicode string in Python 2. | |
891 line = console_to_str(proc.stdout.readline()) | |
892 if not line: | |
893 break | |
894 line = line.rstrip() | |
895 all_output.append(line + '\n') | |
896 | |
897 # Show the line immediately. | |
898 log_subprocess(line) | |
899 # Update the spinner. | |
900 if use_spinner: | |
901 spinner.spin() | |
902 try: | |
903 proc.wait() | |
904 finally: | |
905 if proc.stdout: | |
906 proc.stdout.close() | |
907 proc_had_error = ( | |
908 proc.returncode and proc.returncode not in extra_ok_returncodes | |
909 ) | |
910 if use_spinner: | |
911 if proc_had_error: | |
912 spinner.finish("error") | |
913 else: | |
914 spinner.finish("done") | |
915 if proc_had_error: | |
916 if on_returncode == 'raise': | |
917 if not showing_subprocess: | |
918 # Then the subprocess streams haven't been logged to the | |
919 # console yet. | |
920 msg = make_subprocess_output_error( | |
921 cmd_args=cmd, | |
922 cwd=cwd, | |
923 lines=all_output, | |
924 exit_status=proc.returncode, | |
925 ) | |
926 subprocess_logger.error(msg) | |
927 exc_msg = ( | |
928 'Command errored out with exit status {}: {} ' | |
929 'Check the logs for full command output.' | |
930 ).format(proc.returncode, command_desc) | |
931 raise InstallationError(exc_msg) | |
932 elif on_returncode == 'warn': | |
933 subprocess_logger.warning( | |
934 'Command "%s" had error code %s in %s', | |
935 command_desc, proc.returncode, cwd, | |
936 ) | |
937 elif on_returncode == 'ignore': | |
938 pass | |
939 else: | |
940 raise ValueError('Invalid value: on_returncode=%s' % | |
941 repr(on_returncode)) | |
942 return ''.join(all_output) | |
943 | |
944 | |
945 def _make_build_dir(build_dir): | |
946 os.makedirs(build_dir) | |
947 write_delete_marker_file(build_dir) | |
948 | |
949 | |
950 class FakeFile(object): | |
951 """Wrap a list of lines in an object with readline() to make | |
952 ConfigParser happy.""" | |
953 def __init__(self, lines): | |
954 self._gen = (l for l in lines) | |
955 | |
956 def readline(self): | |
957 try: | |
958 try: | |
959 return next(self._gen) | |
960 except NameError: | |
961 return self._gen.next() | |
962 except StopIteration: | |
963 return '' | |
964 | |
965 def __iter__(self): | |
966 return self._gen | |
967 | |
968 | |
969 class StreamWrapper(StringIO): | |
970 | |
971 @classmethod | |
972 def from_stream(cls, orig_stream): | |
973 cls.orig_stream = orig_stream | |
974 return cls() | |
975 | |
976 # compileall.compile_dir() needs stdout.encoding to print to stdout | |
977 @property | |
978 def encoding(self): | |
979 return self.orig_stream.encoding | |
980 | |
981 | |
982 @contextlib.contextmanager | |
983 def captured_output(stream_name): | |
984 """Return a context manager used by captured_stdout/stdin/stderr | |
985 that temporarily replaces the sys stream *stream_name* with a StringIO. | |
986 | |
987 Taken from Lib/support/__init__.py in the CPython repo. | |
988 """ | |
989 orig_stdout = getattr(sys, stream_name) | |
990 setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout)) | |
991 try: | |
992 yield getattr(sys, stream_name) | |
993 finally: | |
994 setattr(sys, stream_name, orig_stdout) | |
995 | |
996 | |
997 def captured_stdout(): | |
998 """Capture the output of sys.stdout: | |
999 | |
1000 with captured_stdout() as stdout: | |
1001 print('hello') | |
1002 self.assertEqual(stdout.getvalue(), 'hello\n') | |
1003 | |
1004 Taken from Lib/support/__init__.py in the CPython repo. | |
1005 """ | |
1006 return captured_output('stdout') | |
1007 | |
1008 | |
1009 def captured_stderr(): | |
1010 """ | |
1011 See captured_stdout(). | |
1012 """ | |
1013 return captured_output('stderr') | |
1014 | |
1015 | |
1016 class cached_property(object): | |
1017 """A property that is only computed once per instance and then replaces | |
1018 itself with an ordinary attribute. Deleting the attribute resets the | |
1019 property. | |
1020 | |
1021 Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175 | |
1022 """ | |
1023 | |
1024 def __init__(self, func): | |
1025 self.__doc__ = getattr(func, '__doc__') | |
1026 self.func = func | |
1027 | |
1028 def __get__(self, obj, cls): | |
1029 if obj is None: | |
1030 # We're being accessed from the class itself, not from an object | |
1031 return self | |
1032 value = obj.__dict__[self.func.__name__] = self.func(obj) | |
1033 return value | |
1034 | |
1035 | |
1036 def get_installed_version(dist_name, working_set=None): | |
1037 """Get the installed version of dist_name avoiding pkg_resources cache""" | |
1038 # Create a requirement that we'll look for inside of setuptools. | |
1039 req = pkg_resources.Requirement.parse(dist_name) | |
1040 | |
1041 if working_set is None: | |
1042 # We want to avoid having this cached, so we need to construct a new | |
1043 # working set each time. | |
1044 working_set = pkg_resources.WorkingSet() | |
1045 | |
1046 # Get the installed distribution from our working set | |
1047 dist = working_set.find(req) | |
1048 | |
1049 # Check to see if we got an installed distribution or not, if we did | |
1050 # we want to return it's version. | |
1051 return dist.version if dist else None | |
1052 | |
1053 | |
1054 def consume(iterator): | |
1055 """Consume an iterable at C speed.""" | |
1056 deque(iterator, maxlen=0) | |
1057 | |
1058 | |
1059 # Simulates an enum | |
1060 def enum(*sequential, **named): | |
1061 enums = dict(zip(sequential, range(len(sequential))), **named) | |
1062 reverse = {value: key for key, value in enums.items()} | |
1063 enums['reverse_mapping'] = reverse | |
1064 return type('Enum', (), enums) | |
1065 | |
1066 | |
1067 def path_to_url(path): | |
1068 # type: (Union[str, Text]) -> str | |
1069 """ | |
1070 Convert a path to a file: URL. The path will be made absolute and have | |
1071 quoted path parts. | |
1072 """ | |
1073 path = os.path.normpath(os.path.abspath(path)) | |
1074 url = urllib_parse.urljoin('file:', urllib_request.pathname2url(path)) | |
1075 return url | |
1076 | |
1077 | |
1078 def split_auth_from_netloc(netloc): | |
1079 """ | |
1080 Parse out and remove the auth information from a netloc. | |
1081 | |
1082 Returns: (netloc, (username, password)). | |
1083 """ | |
1084 if '@' not in netloc: | |
1085 return netloc, (None, None) | |
1086 | |
1087 # Split from the right because that's how urllib.parse.urlsplit() | |
1088 # behaves if more than one @ is present (which can be checked using | |
1089 # the password attribute of urlsplit()'s return value). | |
1090 auth, netloc = netloc.rsplit('@', 1) | |
1091 if ':' in auth: | |
1092 # Split from the left because that's how urllib.parse.urlsplit() | |
1093 # behaves if more than one : is present (which again can be checked | |
1094 # using the password attribute of the return value) | |
1095 user_pass = auth.split(':', 1) | |
1096 else: | |
1097 user_pass = auth, None | |
1098 | |
1099 user_pass = tuple( | |
1100 None if x is None else urllib_unquote(x) for x in user_pass | |
1101 ) | |
1102 | |
1103 return netloc, user_pass | |
1104 | |
1105 | |
1106 def redact_netloc(netloc): | |
1107 # type: (str) -> str | |
1108 """ | |
1109 Replace the password in a netloc with "****", if it exists. | |
1110 | |
1111 For example, "user:pass@example.com" returns "user:****@example.com". | |
1112 """ | |
1113 netloc, (user, password) = split_auth_from_netloc(netloc) | |
1114 if user is None: | |
1115 return netloc | |
1116 password = '' if password is None else ':****' | |
1117 return '{user}{password}@{netloc}'.format(user=urllib_parse.quote(user), | |
1118 password=password, | |
1119 netloc=netloc) | |
1120 | |
1121 | |
1122 def _transform_url(url, transform_netloc): | |
1123 """Transform and replace netloc in a url. | |
1124 | |
1125 transform_netloc is a function taking the netloc and returning a | |
1126 tuple. The first element of this tuple is the new netloc. The | |
1127 entire tuple is returned. | |
1128 | |
1129 Returns a tuple containing the transformed url as item 0 and the | |
1130 original tuple returned by transform_netloc as item 1. | |
1131 """ | |
1132 purl = urllib_parse.urlsplit(url) | |
1133 netloc_tuple = transform_netloc(purl.netloc) | |
1134 # stripped url | |
1135 url_pieces = ( | |
1136 purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment | |
1137 ) | |
1138 surl = urllib_parse.urlunsplit(url_pieces) | |
1139 return surl, netloc_tuple | |
1140 | |
1141 | |
1142 def _get_netloc(netloc): | |
1143 return split_auth_from_netloc(netloc) | |
1144 | |
1145 | |
1146 def _redact_netloc(netloc): | |
1147 return (redact_netloc(netloc),) | |
1148 | |
1149 | |
1150 def split_auth_netloc_from_url(url): | |
1151 # type: (str) -> Tuple[str, str, Tuple[str, str]] | |
1152 """ | |
1153 Parse a url into separate netloc, auth, and url with no auth. | |
1154 | |
1155 Returns: (url_without_auth, netloc, (username, password)) | |
1156 """ | |
1157 url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc) | |
1158 return url_without_auth, netloc, auth | |
1159 | |
1160 | |
1161 def remove_auth_from_url(url): | |
1162 # type: (str) -> str | |
1163 """Return a copy of url with 'username:password@' removed.""" | |
1164 # username/pass params are passed to subversion through flags | |
1165 # and are not recognized in the url. | |
1166 return _transform_url(url, _get_netloc)[0] | |
1167 | |
1168 | |
1169 def redact_password_from_url(url): | |
1170 # type: (str) -> str | |
1171 """Replace the password in a given url with ****.""" | |
1172 return _transform_url(url, _redact_netloc)[0] | |
1173 | |
1174 | |
1175 def protect_pip_from_modification_on_windows(modifying_pip): | |
1176 """Protection of pip.exe from modification on Windows | |
1177 | |
1178 On Windows, any operation modifying pip should be run as: | |
1179 python -m pip ... | |
1180 """ | |
1181 pip_names = [ | |
1182 "pip.exe", | |
1183 "pip{}.exe".format(sys.version_info[0]), | |
1184 "pip{}.{}.exe".format(*sys.version_info[:2]) | |
1185 ] | |
1186 | |
1187 # See https://github.com/pypa/pip/issues/1299 for more discussion | |
1188 should_show_use_python_msg = ( | |
1189 modifying_pip and | |
1190 WINDOWS and | |
1191 os.path.basename(sys.argv[0]) in pip_names | |
1192 ) | |
1193 | |
1194 if should_show_use_python_msg: | |
1195 new_command = [ | |
1196 sys.executable, "-m", "pip" | |
1197 ] + sys.argv[1:] | |
1198 raise CommandError( | |
1199 'To modify pip, please run the following command:\n{}' | |
1200 .format(" ".join(new_command)) | |
1201 ) |