You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

misc.py 33KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  1. from __future__ import absolute_import
  2. import contextlib
  3. import errno
  4. import io
  5. import locale
  6. # we have a submodule named 'logging' which would shadow this if we used the
  7. # regular name:
  8. import logging as std_logging
  9. import os
  10. import posixpath
  11. import re
  12. import shutil
  13. import stat
  14. import subprocess
  15. import sys
  16. import tarfile
  17. import zipfile
  18. from collections import deque
  19. from pip._vendor import pkg_resources
  20. # NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
  21. # why we ignore the type on this import.
  22. from pip._vendor.retrying import retry # type: ignore
  23. from pip._vendor.six import PY2
  24. from pip._vendor.six.moves import input
  25. from pip._vendor.six.moves.urllib import parse as urllib_parse
  26. from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
  27. from pip._internal.exceptions import CommandError, InstallationError
  28. from pip._internal.locations import (
  29. running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
  30. write_delete_marker_file,
  31. )
  32. from pip._internal.utils.compat import (
  33. WINDOWS, console_to_str, expanduser, stdlib_pkgs,
  34. )
  35. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  36. if PY2:
  37. from io import BytesIO as StringIO
  38. else:
  39. from io import StringIO
  40. if MYPY_CHECK_RUNNING:
  41. from typing import ( # noqa: F401
  42. Optional, Tuple, Iterable, List, Match, Union, Any, Mapping, Text,
  43. AnyStr, Container
  44. )
  45. from pip._vendor.pkg_resources import Distribution # noqa: F401
  46. from pip._internal.models.link import Link # noqa: F401
  47. from pip._internal.utils.ui import SpinnerInterface # noqa: F401
  48. __all__ = ['rmtree', 'display_path', 'backup_dir',
  49. 'ask', 'splitext',
  50. 'format_size', 'is_installable_dir',
  51. 'is_svn_page', 'file_contents',
  52. 'split_leading_dir', 'has_leading_dir',
  53. 'normalize_path',
  54. 'renames', 'get_prog',
  55. 'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess',
  56. 'captured_stdout', 'ensure_dir',
  57. 'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS', 'WHEEL_EXTENSION',
  58. 'get_installed_version', 'remove_auth_from_url']
  59. logger = std_logging.getLogger(__name__)
  60. WHEEL_EXTENSION = '.whl'
  61. BZ2_EXTENSIONS = ('.tar.bz2', '.tbz')
  62. XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma')
  63. ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION)
  64. TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar')
  65. ARCHIVE_EXTENSIONS = (
  66. ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS)
  67. SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS
  68. try:
  69. import bz2 # noqa
  70. SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
  71. except ImportError:
  72. logger.debug('bz2 module is not available')
  73. try:
  74. # Only for Python 3.3+
  75. import lzma # noqa
  76. SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
  77. except ImportError:
  78. logger.debug('lzma module is not available')
  79. def ensure_dir(path):
  80. # type: (AnyStr) -> None
  81. """os.path.makedirs without EEXIST."""
  82. try:
  83. os.makedirs(path)
  84. except OSError as e:
  85. if e.errno != errno.EEXIST:
  86. raise
  87. def get_prog():
  88. # type: () -> str
  89. try:
  90. prog = os.path.basename(sys.argv[0])
  91. if prog in ('__main__.py', '-c'):
  92. return "%s -m pip" % sys.executable
  93. else:
  94. return prog
  95. except (AttributeError, TypeError, IndexError):
  96. pass
  97. return 'pip'
  98. # Retry every half second for up to 3 seconds
  99. @retry(stop_max_delay=3000, wait_fixed=500)
  100. def rmtree(dir, ignore_errors=False):
  101. # type: (str, bool) -> None
  102. shutil.rmtree(dir, ignore_errors=ignore_errors,
  103. onerror=rmtree_errorhandler)
  104. def rmtree_errorhandler(func, path, exc_info):
  105. """On Windows, the files in .svn are read-only, so when rmtree() tries to
  106. remove them, an exception is thrown. We catch that here, remove the
  107. read-only attribute, and hopefully continue without problems."""
  108. # if file type currently read only
  109. if os.stat(path).st_mode & stat.S_IREAD:
  110. # convert to read/write
  111. os.chmod(path, stat.S_IWRITE)
  112. # use the original function to repeat the operation
  113. func(path)
  114. return
  115. else:
  116. raise
  117. def display_path(path):
  118. # type: (Union[str, Text]) -> str
  119. """Gives the display value for a given path, making it relative to cwd
  120. if possible."""
  121. path = os.path.normcase(os.path.abspath(path))
  122. if sys.version_info[0] == 2:
  123. path = path.decode(sys.getfilesystemencoding(), 'replace')
  124. path = path.encode(sys.getdefaultencoding(), 'replace')
  125. if path.startswith(os.getcwd() + os.path.sep):
  126. path = '.' + path[len(os.getcwd()):]
  127. return path
  128. def backup_dir(dir, ext='.bak'):
  129. # type: (str, str) -> str
  130. """Figure out the name of a directory to back up the given dir to
  131. (adding .bak, .bak2, etc)"""
  132. n = 1
  133. extension = ext
  134. while os.path.exists(dir + extension):
  135. n += 1
  136. extension = ext + str(n)
  137. return dir + extension
  138. def ask_path_exists(message, options):
  139. # type: (str, Iterable[str]) -> str
  140. for action in os.environ.get('PIP_EXISTS_ACTION', '').split():
  141. if action in options:
  142. return action
  143. return ask(message, options)
  144. def ask(message, options):
  145. # type: (str, Iterable[str]) -> str
  146. """Ask the message interactively, with the given possible responses"""
  147. while 1:
  148. if os.environ.get('PIP_NO_INPUT'):
  149. raise Exception(
  150. 'No input was expected ($PIP_NO_INPUT set); question: %s' %
  151. message
  152. )
  153. response = input(message)
  154. response = response.strip().lower()
  155. if response not in options:
  156. print(
  157. 'Your response (%r) was not one of the expected responses: '
  158. '%s' % (response, ', '.join(options))
  159. )
  160. else:
  161. return response
  162. def format_size(bytes):
  163. # type: (float) -> str
  164. if bytes > 1000 * 1000:
  165. return '%.1fMB' % (bytes / 1000.0 / 1000)
  166. elif bytes > 10 * 1000:
  167. return '%ikB' % (bytes / 1000)
  168. elif bytes > 1000:
  169. return '%.1fkB' % (bytes / 1000.0)
  170. else:
  171. return '%ibytes' % bytes
  172. def is_installable_dir(path):
  173. # type: (str) -> bool
  174. """Is path is a directory containing setup.py or pyproject.toml?
  175. """
  176. if not os.path.isdir(path):
  177. return False
  178. setup_py = os.path.join(path, 'setup.py')
  179. if os.path.isfile(setup_py):
  180. return True
  181. pyproject_toml = os.path.join(path, 'pyproject.toml')
  182. if os.path.isfile(pyproject_toml):
  183. return True
  184. return False
  185. def is_svn_page(html):
  186. # type: (Union[str, Text]) -> Optional[Match[Union[str, Text]]]
  187. """
  188. Returns true if the page appears to be the index page of an svn repository
  189. """
  190. return (re.search(r'<title>[^<]*Revision \d+:', html) and
  191. re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I))
  192. def file_contents(filename):
  193. # type: (str) -> Text
  194. with open(filename, 'rb') as fp:
  195. return fp.read().decode('utf-8')
  196. def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
  197. """Yield pieces of data from a file-like object until EOF."""
  198. while True:
  199. chunk = file.read(size)
  200. if not chunk:
  201. break
  202. yield chunk
  203. def split_leading_dir(path):
  204. # type: (Union[str, Text]) -> List[Union[str, Text]]
  205. path = path.lstrip('/').lstrip('\\')
  206. if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or
  207. '\\' not in path):
  208. return path.split('/', 1)
  209. elif '\\' in path:
  210. return path.split('\\', 1)
  211. else:
  212. return [path, '']
  213. def has_leading_dir(paths):
  214. # type: (Iterable[Union[str, Text]]) -> bool
  215. """Returns true if all the paths have the same leading path name
  216. (i.e., everything is in one subdirectory in an archive)"""
  217. common_prefix = None
  218. for path in paths:
  219. prefix, rest = split_leading_dir(path)
  220. if not prefix:
  221. return False
  222. elif common_prefix is None:
  223. common_prefix = prefix
  224. elif prefix != common_prefix:
  225. return False
  226. return True
  227. def normalize_path(path, resolve_symlinks=True):
  228. # type: (str, bool) -> str
  229. """
  230. Convert a path to its canonical, case-normalized, absolute version.
  231. """
  232. path = expanduser(path)
  233. if resolve_symlinks:
  234. path = os.path.realpath(path)
  235. else:
  236. path = os.path.abspath(path)
  237. return os.path.normcase(path)
  238. def splitext(path):
  239. # type: (str) -> Tuple[str, str]
  240. """Like os.path.splitext, but take off .tar too"""
  241. base, ext = posixpath.splitext(path)
  242. if base.lower().endswith('.tar'):
  243. ext = base[-4:] + ext
  244. base = base[:-4]
  245. return base, ext
  246. def renames(old, new):
  247. # type: (str, str) -> None
  248. """Like os.renames(), but handles renaming across devices."""
  249. # Implementation borrowed from os.renames().
  250. head, tail = os.path.split(new)
  251. if head and tail and not os.path.exists(head):
  252. os.makedirs(head)
  253. shutil.move(old, new)
  254. head, tail = os.path.split(old)
  255. if head and tail:
  256. try:
  257. os.removedirs(head)
  258. except OSError:
  259. pass
  260. def is_local(path):
  261. # type: (str) -> bool
  262. """
  263. Return True if path is within sys.prefix, if we're running in a virtualenv.
  264. If we're not in a virtualenv, all paths are considered "local."
  265. """
  266. if not running_under_virtualenv():
  267. return True
  268. return normalize_path(path).startswith(normalize_path(sys.prefix))
  269. def dist_is_local(dist):
  270. # type: (Distribution) -> bool
  271. """
  272. Return True if given Distribution object is installed locally
  273. (i.e. within current virtualenv).
  274. Always True if we're not in a virtualenv.
  275. """
  276. return is_local(dist_location(dist))
  277. def dist_in_usersite(dist):
  278. # type: (Distribution) -> bool
  279. """
  280. Return True if given Distribution is installed in user site.
  281. """
  282. norm_path = normalize_path(dist_location(dist))
  283. return norm_path.startswith(normalize_path(user_site))
  284. def dist_in_site_packages(dist):
  285. # type: (Distribution) -> bool
  286. """
  287. Return True if given Distribution is installed in
  288. sysconfig.get_python_lib().
  289. """
  290. return normalize_path(
  291. dist_location(dist)
  292. ).startswith(normalize_path(site_packages))
  293. def dist_is_editable(dist):
  294. # type: (Distribution) -> bool
  295. """
  296. Return True if given Distribution is an editable install.
  297. """
  298. for path_item in sys.path:
  299. egg_link = os.path.join(path_item, dist.project_name + '.egg-link')
  300. if os.path.isfile(egg_link):
  301. return True
  302. return False
  303. def get_installed_distributions(local_only=True,
  304. skip=stdlib_pkgs,
  305. include_editables=True,
  306. editables_only=False,
  307. user_only=False):
  308. # type: (bool, Container[str], bool, bool, bool) -> List[Distribution]
  309. """
  310. Return a list of installed Distribution objects.
  311. If ``local_only`` is True (default), only return installations
  312. local to the current virtualenv, if in a virtualenv.
  313. ``skip`` argument is an iterable of lower-case project names to
  314. ignore; defaults to stdlib_pkgs
  315. If ``include_editables`` is False, don't report editables.
  316. If ``editables_only`` is True , only report editables.
  317. If ``user_only`` is True , only report installations in the user
  318. site directory.
  319. """
  320. if local_only:
  321. local_test = dist_is_local
  322. else:
  323. def local_test(d):
  324. return True
  325. if include_editables:
  326. def editable_test(d):
  327. return True
  328. else:
  329. def editable_test(d):
  330. return not dist_is_editable(d)
  331. if editables_only:
  332. def editables_only_test(d):
  333. return dist_is_editable(d)
  334. else:
  335. def editables_only_test(d):
  336. return True
  337. if user_only:
  338. user_test = dist_in_usersite
  339. else:
  340. def user_test(d):
  341. return True
  342. # because of pkg_resources vendoring, mypy cannot find stub in typeshed
  343. return [d for d in pkg_resources.working_set # type: ignore
  344. if local_test(d) and
  345. d.key not in skip and
  346. editable_test(d) and
  347. editables_only_test(d) and
  348. user_test(d)
  349. ]
  350. def egg_link_path(dist):
  351. # type: (Distribution) -> Optional[str]
  352. """
  353. Return the path for the .egg-link file if it exists, otherwise, None.
  354. There's 3 scenarios:
  355. 1) not in a virtualenv
  356. try to find in site.USER_SITE, then site_packages
  357. 2) in a no-global virtualenv
  358. try to find in site_packages
  359. 3) in a yes-global virtualenv
  360. try to find in site_packages, then site.USER_SITE
  361. (don't look in global location)
  362. For #1 and #3, there could be odd cases, where there's an egg-link in 2
  363. locations.
  364. This method will just return the first one found.
  365. """
  366. sites = []
  367. if running_under_virtualenv():
  368. if virtualenv_no_global():
  369. sites.append(site_packages)
  370. else:
  371. sites.append(site_packages)
  372. if user_site:
  373. sites.append(user_site)
  374. else:
  375. if user_site:
  376. sites.append(user_site)
  377. sites.append(site_packages)
  378. for site in sites:
  379. egglink = os.path.join(site, dist.project_name) + '.egg-link'
  380. if os.path.isfile(egglink):
  381. return egglink
  382. return None
  383. def dist_location(dist):
  384. # type: (Distribution) -> str
  385. """
  386. Get the site-packages location of this distribution. Generally
  387. this is dist.location, except in the case of develop-installed
  388. packages, where dist.location is the source code location, and we
  389. want to know where the egg-link file is.
  390. """
  391. egg_link = egg_link_path(dist)
  392. if egg_link:
  393. return egg_link
  394. return dist.location
  395. def current_umask():
  396. """Get the current umask which involves having to set it temporarily."""
  397. mask = os.umask(0)
  398. os.umask(mask)
  399. return mask
  400. def unzip_file(filename, location, flatten=True):
  401. # type: (str, str, bool) -> None
  402. """
  403. Unzip the file (with path `filename`) to the destination `location`. All
  404. files are written based on system defaults and umask (i.e. permissions are
  405. not preserved), except that regular file members with any execute
  406. permissions (user, group, or world) have "chmod +x" applied after being
  407. written. Note that for windows, any execute changes using os.chmod are
  408. no-ops per the python docs.
  409. """
  410. ensure_dir(location)
  411. zipfp = open(filename, 'rb')
  412. try:
  413. zip = zipfile.ZipFile(zipfp, allowZip64=True)
  414. leading = has_leading_dir(zip.namelist()) and flatten
  415. for info in zip.infolist():
  416. name = info.filename
  417. fn = name
  418. if leading:
  419. fn = split_leading_dir(name)[1]
  420. fn = os.path.join(location, fn)
  421. dir = os.path.dirname(fn)
  422. if fn.endswith('/') or fn.endswith('\\'):
  423. # A directory
  424. ensure_dir(fn)
  425. else:
  426. ensure_dir(dir)
  427. # Don't use read() to avoid allocating an arbitrarily large
  428. # chunk of memory for the file's content
  429. fp = zip.open(name)
  430. try:
  431. with open(fn, 'wb') as destfp:
  432. shutil.copyfileobj(fp, destfp)
  433. finally:
  434. fp.close()
  435. mode = info.external_attr >> 16
  436. # if mode and regular file and any execute permissions for
  437. # user/group/world?
  438. if mode and stat.S_ISREG(mode) and mode & 0o111:
  439. # make dest file have execute for user/group/world
  440. # (chmod +x) no-op on windows per python docs
  441. os.chmod(fn, (0o777 - current_umask() | 0o111))
  442. finally:
  443. zipfp.close()
  444. def untar_file(filename, location):
  445. # type: (str, str) -> None
  446. """
  447. Untar the file (with path `filename`) to the destination `location`.
  448. All files are written based on system defaults and umask (i.e. permissions
  449. are not preserved), except that regular file members with any execute
  450. permissions (user, group, or world) have "chmod +x" applied after being
  451. written. Note that for windows, any execute changes using os.chmod are
  452. no-ops per the python docs.
  453. """
  454. ensure_dir(location)
  455. if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
  456. mode = 'r:gz'
  457. elif filename.lower().endswith(BZ2_EXTENSIONS):
  458. mode = 'r:bz2'
  459. elif filename.lower().endswith(XZ_EXTENSIONS):
  460. mode = 'r:xz'
  461. elif filename.lower().endswith('.tar'):
  462. mode = 'r'
  463. else:
  464. logger.warning(
  465. 'Cannot determine compression type for file %s', filename,
  466. )
  467. mode = 'r:*'
  468. tar = tarfile.open(filename, mode)
  469. try:
  470. leading = has_leading_dir([
  471. member.name for member in tar.getmembers()
  472. ])
  473. for member in tar.getmembers():
  474. fn = member.name
  475. if leading:
  476. # https://github.com/python/mypy/issues/1174
  477. fn = split_leading_dir(fn)[1] # type: ignore
  478. path = os.path.join(location, fn)
  479. if member.isdir():
  480. ensure_dir(path)
  481. elif member.issym():
  482. try:
  483. # https://github.com/python/typeshed/issues/2673
  484. tar._extract_member(member, path) # type: ignore
  485. except Exception as exc:
  486. # Some corrupt tar files seem to produce this
  487. # (specifically bad symlinks)
  488. logger.warning(
  489. 'In the tar file %s the member %s is invalid: %s',
  490. filename, member.name, exc,
  491. )
  492. continue
  493. else:
  494. try:
  495. fp = tar.extractfile(member)
  496. except (KeyError, AttributeError) as exc:
  497. # Some corrupt tar files seem to produce this
  498. # (specifically bad symlinks)
  499. logger.warning(
  500. 'In the tar file %s the member %s is invalid: %s',
  501. filename, member.name, exc,
  502. )
  503. continue
  504. ensure_dir(os.path.dirname(path))
  505. with open(path, 'wb') as destfp:
  506. shutil.copyfileobj(fp, destfp)
  507. fp.close()
  508. # Update the timestamp (useful for cython compiled files)
  509. # https://github.com/python/typeshed/issues/2673
  510. tar.utime(member, path) # type: ignore
  511. # member have any execute permissions for user/group/world?
  512. if member.mode & 0o111:
  513. # make dest file have execute for user/group/world
  514. # no-op on windows per python docs
  515. os.chmod(path, (0o777 - current_umask() | 0o111))
  516. finally:
  517. tar.close()
  518. def unpack_file(
  519. filename, # type: str
  520. location, # type: str
  521. content_type, # type: Optional[str]
  522. link # type: Optional[Link]
  523. ):
  524. # type: (...) -> None
  525. filename = os.path.realpath(filename)
  526. if (content_type == 'application/zip' or
  527. filename.lower().endswith(ZIP_EXTENSIONS) or
  528. zipfile.is_zipfile(filename)):
  529. unzip_file(
  530. filename,
  531. location,
  532. flatten=not filename.endswith('.whl')
  533. )
  534. elif (content_type == 'application/x-gzip' or
  535. tarfile.is_tarfile(filename) or
  536. filename.lower().endswith(
  537. TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)):
  538. untar_file(filename, location)
  539. elif (content_type and content_type.startswith('text/html') and
  540. is_svn_page(file_contents(filename))):
  541. # We don't really care about this
  542. from pip._internal.vcs.subversion import Subversion
  543. Subversion('svn+' + link.url).unpack(location)
  544. else:
  545. # FIXME: handle?
  546. # FIXME: magic signatures?
  547. logger.critical(
  548. 'Cannot unpack file %s (downloaded from %s, content-type: %s); '
  549. 'cannot detect archive format',
  550. filename, location, content_type,
  551. )
  552. raise InstallationError(
  553. 'Cannot determine archive format of %s' % location
  554. )
  555. def call_subprocess(
  556. cmd, # type: List[str]
  557. show_stdout=True, # type: bool
  558. cwd=None, # type: Optional[str]
  559. on_returncode='raise', # type: str
  560. extra_ok_returncodes=None, # type: Optional[Iterable[int]]
  561. command_desc=None, # type: Optional[str]
  562. extra_environ=None, # type: Optional[Mapping[str, Any]]
  563. unset_environ=None, # type: Optional[Iterable[str]]
  564. spinner=None # type: Optional[SpinnerInterface]
  565. ):
  566. # type: (...) -> Optional[Text]
  567. """
  568. Args:
  569. extra_ok_returncodes: an iterable of integer return codes that are
  570. acceptable, in addition to 0. Defaults to None, which means [].
  571. unset_environ: an iterable of environment variable names to unset
  572. prior to calling subprocess.Popen().
  573. """
  574. if extra_ok_returncodes is None:
  575. extra_ok_returncodes = []
  576. if unset_environ is None:
  577. unset_environ = []
  578. # This function's handling of subprocess output is confusing and I
  579. # previously broke it terribly, so as penance I will write a long comment
  580. # explaining things.
  581. #
  582. # The obvious thing that affects output is the show_stdout=
  583. # kwarg. show_stdout=True means, let the subprocess write directly to our
  584. # stdout. Even though it is nominally the default, it is almost never used
  585. # inside pip (and should not be used in new code without a very good
  586. # reason); as of 2016-02-22 it is only used in a few places inside the VCS
  587. # wrapper code. Ideally we should get rid of it entirely, because it
  588. # creates a lot of complexity here for a rarely used feature.
  589. #
  590. # Most places in pip set show_stdout=False. What this means is:
  591. # - We connect the child stdout to a pipe, which we read.
  592. # - By default, we hide the output but show a spinner -- unless the
  593. # subprocess exits with an error, in which case we show the output.
  594. # - If the --verbose option was passed (= loglevel is DEBUG), then we show
  595. # the output unconditionally. (But in this case we don't want to show
  596. # the output a second time if it turns out that there was an error.)
  597. #
  598. # stderr is always merged with stdout (even if show_stdout=True).
  599. if show_stdout:
  600. stdout = None
  601. else:
  602. stdout = subprocess.PIPE
  603. if command_desc is None:
  604. cmd_parts = []
  605. for part in cmd:
  606. if ' ' in part or '\n' in part or '"' in part or "'" in part:
  607. part = '"%s"' % part.replace('"', '\\"')
  608. cmd_parts.append(part)
  609. command_desc = ' '.join(cmd_parts)
  610. logger.debug("Running command %s", command_desc)
  611. env = os.environ.copy()
  612. if extra_environ:
  613. env.update(extra_environ)
  614. for name in unset_environ:
  615. env.pop(name, None)
  616. try:
  617. proc = subprocess.Popen(
  618. cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
  619. stdout=stdout, cwd=cwd, env=env,
  620. )
  621. proc.stdin.close()
  622. except Exception as exc:
  623. logger.critical(
  624. "Error %s while executing command %s", exc, command_desc,
  625. )
  626. raise
  627. all_output = []
  628. if stdout is not None:
  629. while True:
  630. line = console_to_str(proc.stdout.readline())
  631. if not line:
  632. break
  633. line = line.rstrip()
  634. all_output.append(line + '\n')
  635. if logger.getEffectiveLevel() <= std_logging.DEBUG:
  636. # Show the line immediately
  637. logger.debug(line)
  638. else:
  639. # Update the spinner
  640. if spinner is not None:
  641. spinner.spin()
  642. try:
  643. proc.wait()
  644. finally:
  645. if proc.stdout:
  646. proc.stdout.close()
  647. if spinner is not None:
  648. if proc.returncode:
  649. spinner.finish("error")
  650. else:
  651. spinner.finish("done")
  652. if proc.returncode and proc.returncode not in extra_ok_returncodes:
  653. if on_returncode == 'raise':
  654. if (logger.getEffectiveLevel() > std_logging.DEBUG and
  655. not show_stdout):
  656. logger.info(
  657. 'Complete output from command %s:', command_desc,
  658. )
  659. logger.info(
  660. ''.join(all_output) +
  661. '\n----------------------------------------'
  662. )
  663. raise InstallationError(
  664. 'Command "%s" failed with error code %s in %s'
  665. % (command_desc, proc.returncode, cwd))
  666. elif on_returncode == 'warn':
  667. logger.warning(
  668. 'Command "%s" had error code %s in %s',
  669. command_desc, proc.returncode, cwd,
  670. )
  671. elif on_returncode == 'ignore':
  672. pass
  673. else:
  674. raise ValueError('Invalid value: on_returncode=%s' %
  675. repr(on_returncode))
  676. if not show_stdout:
  677. return ''.join(all_output)
  678. return None
  679. def read_text_file(filename):
  680. # type: (str) -> str
  681. """Return the contents of *filename*.
  682. Try to decode the file contents with utf-8, the preferred system encoding
  683. (e.g., cp1252 on some Windows machines), and latin1, in that order.
  684. Decoding a byte string with latin1 will never raise an error. In the worst
  685. case, the returned string will contain some garbage characters.
  686. """
  687. with open(filename, 'rb') as fp:
  688. data = fp.read()
  689. encodings = ['utf-8', locale.getpreferredencoding(False), 'latin1']
  690. for enc in encodings:
  691. try:
  692. # https://github.com/python/mypy/issues/1174
  693. data = data.decode(enc) # type: ignore
  694. except UnicodeDecodeError:
  695. continue
  696. break
  697. assert not isinstance(data, bytes) # Latin1 should have worked.
  698. return data
  699. def _make_build_dir(build_dir):
  700. os.makedirs(build_dir)
  701. write_delete_marker_file(build_dir)
  702. class FakeFile(object):
  703. """Wrap a list of lines in an object with readline() to make
  704. ConfigParser happy."""
  705. def __init__(self, lines):
  706. self._gen = (l for l in lines)
  707. def readline(self):
  708. try:
  709. try:
  710. return next(self._gen)
  711. except NameError:
  712. return self._gen.next()
  713. except StopIteration:
  714. return ''
  715. def __iter__(self):
  716. return self._gen
  717. class StreamWrapper(StringIO):
  718. @classmethod
  719. def from_stream(cls, orig_stream):
  720. cls.orig_stream = orig_stream
  721. return cls()
  722. # compileall.compile_dir() needs stdout.encoding to print to stdout
  723. @property
  724. def encoding(self):
  725. return self.orig_stream.encoding
  726. @contextlib.contextmanager
  727. def captured_output(stream_name):
  728. """Return a context manager used by captured_stdout/stdin/stderr
  729. that temporarily replaces the sys stream *stream_name* with a StringIO.
  730. Taken from Lib/support/__init__.py in the CPython repo.
  731. """
  732. orig_stdout = getattr(sys, stream_name)
  733. setattr(sys, stream_name, StreamWrapper.from_stream(orig_stdout))
  734. try:
  735. yield getattr(sys, stream_name)
  736. finally:
  737. setattr(sys, stream_name, orig_stdout)
  738. def captured_stdout():
  739. """Capture the output of sys.stdout:
  740. with captured_stdout() as stdout:
  741. print('hello')
  742. self.assertEqual(stdout.getvalue(), 'hello\n')
  743. Taken from Lib/support/__init__.py in the CPython repo.
  744. """
  745. return captured_output('stdout')
  746. def captured_stderr():
  747. """
  748. See captured_stdout().
  749. """
  750. return captured_output('stderr')
  751. class cached_property(object):
  752. """A property that is only computed once per instance and then replaces
  753. itself with an ordinary attribute. Deleting the attribute resets the
  754. property.
  755. Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175
  756. """
  757. def __init__(self, func):
  758. self.__doc__ = getattr(func, '__doc__')
  759. self.func = func
  760. def __get__(self, obj, cls):
  761. if obj is None:
  762. # We're being accessed from the class itself, not from an object
  763. return self
  764. value = obj.__dict__[self.func.__name__] = self.func(obj)
  765. return value
  766. def get_installed_version(dist_name, working_set=None):
  767. """Get the installed version of dist_name avoiding pkg_resources cache"""
  768. # Create a requirement that we'll look for inside of setuptools.
  769. req = pkg_resources.Requirement.parse(dist_name)
  770. if working_set is None:
  771. # We want to avoid having this cached, so we need to construct a new
  772. # working set each time.
  773. working_set = pkg_resources.WorkingSet()
  774. # Get the installed distribution from our working set
  775. dist = working_set.find(req)
  776. # Check to see if we got an installed distribution or not, if we did
  777. # we want to return it's version.
  778. return dist.version if dist else None
  779. def consume(iterator):
  780. """Consume an iterable at C speed."""
  781. deque(iterator, maxlen=0)
  782. # Simulates an enum
  783. def enum(*sequential, **named):
  784. enums = dict(zip(sequential, range(len(sequential))), **named)
  785. reverse = {value: key for key, value in enums.items()}
  786. enums['reverse_mapping'] = reverse
  787. return type('Enum', (), enums)
  788. def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
  789. """
  790. Return the URL for a VCS requirement.
  791. Args:
  792. repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+").
  793. project_name: the (unescaped) project name.
  794. """
  795. egg_project_name = pkg_resources.to_filename(project_name)
  796. req = '{}@{}#egg={}'.format(repo_url, rev, egg_project_name)
  797. if subdir:
  798. req += '&subdirectory={}'.format(subdir)
  799. return req
  800. def split_auth_from_netloc(netloc):
  801. """
  802. Parse out and remove the auth information from a netloc.
  803. Returns: (netloc, (username, password)).
  804. """
  805. if '@' not in netloc:
  806. return netloc, (None, None)
  807. # Split from the right because that's how urllib.parse.urlsplit()
  808. # behaves if more than one @ is present (which can be checked using
  809. # the password attribute of urlsplit()'s return value).
  810. auth, netloc = netloc.rsplit('@', 1)
  811. if ':' in auth:
  812. # Split from the left because that's how urllib.parse.urlsplit()
  813. # behaves if more than one : is present (which again can be checked
  814. # using the password attribute of the return value)
  815. user_pass = auth.split(':', 1)
  816. else:
  817. user_pass = auth, None
  818. user_pass = tuple(
  819. None if x is None else urllib_unquote(x) for x in user_pass
  820. )
  821. return netloc, user_pass
  822. def redact_netloc(netloc):
  823. # type: (str) -> str
  824. """
  825. Replace the password in a netloc with "****", if it exists.
  826. For example, "user:pass@example.com" returns "user:****@example.com".
  827. """
  828. netloc, (user, password) = split_auth_from_netloc(netloc)
  829. if user is None:
  830. return netloc
  831. password = '' if password is None else ':****'
  832. return '{user}{password}@{netloc}'.format(user=urllib_parse.quote(user),
  833. password=password,
  834. netloc=netloc)
  835. def _transform_url(url, transform_netloc):
  836. purl = urllib_parse.urlsplit(url)
  837. netloc = transform_netloc(purl.netloc)
  838. # stripped url
  839. url_pieces = (
  840. purl.scheme, netloc, purl.path, purl.query, purl.fragment
  841. )
  842. surl = urllib_parse.urlunsplit(url_pieces)
  843. return surl
  844. def _get_netloc(netloc):
  845. return split_auth_from_netloc(netloc)[0]
  846. def remove_auth_from_url(url):
  847. # type: (str) -> str
  848. # Return a copy of url with 'username:password@' removed.
  849. # username/pass params are passed to subversion through flags
  850. # and are not recognized in the url.
  851. return _transform_url(url, _get_netloc)
  852. def redact_password_from_url(url):
  853. # type: (str) -> str
  854. """Replace the password in a given url with ****."""
  855. return _transform_url(url, redact_netloc)
  856. def protect_pip_from_modification_on_windows(modifying_pip):
  857. """Protection of pip.exe from modification on Windows
  858. On Windows, any operation modifying pip should be run as:
  859. python -m pip ...
  860. """
  861. pip_names = [
  862. "pip.exe",
  863. "pip{}.exe".format(sys.version_info[0]),
  864. "pip{}.{}.exe".format(*sys.version_info[:2])
  865. ]
  866. # See https://github.com/pypa/pip/issues/1299 for more discussion
  867. should_show_use_python_msg = (
  868. modifying_pip and
  869. WINDOWS and
  870. os.path.basename(sys.argv[0]) in pip_names
  871. )
  872. if should_show_use_python_msg:
  873. new_command = [
  874. sys.executable, "-m", "pip"
  875. ] + sys.argv[1:]
  876. raise CommandError(
  877. 'To modify pip, please run the following command:\n{}'
  878. .format(" ".join(new_command))
  879. )