installer.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import glob
  2. import os
  3. import subprocess
  4. import sys
  5. from distutils import log
  6. from distutils.errors import DistutilsError
  7. import pkg_resources
  8. from setuptools.command.easy_install import easy_install
  9. from setuptools.extern import six
  10. from setuptools.wheel import Wheel
  11. from .py31compat import TemporaryDirectory
  12. def _fixup_find_links(find_links):
  13. """Ensure find-links option end-up being a list of strings."""
  14. if isinstance(find_links, six.string_types):
  15. return find_links.split()
  16. assert isinstance(find_links, (tuple, list))
  17. return find_links
  18. def _legacy_fetch_build_egg(dist, req):
  19. """Fetch an egg needed for building.
  20. Legacy path using EasyInstall.
  21. """
  22. tmp_dist = dist.__class__({'script_args': ['easy_install']})
  23. opts = tmp_dist.get_option_dict('easy_install')
  24. opts.clear()
  25. opts.update(
  26. (k, v)
  27. for k, v in dist.get_option_dict('easy_install').items()
  28. if k in (
  29. # don't use any other settings
  30. 'find_links', 'site_dirs', 'index_url',
  31. 'optimize', 'site_dirs', 'allow_hosts',
  32. ))
  33. if dist.dependency_links:
  34. links = dist.dependency_links[:]
  35. if 'find_links' in opts:
  36. links = _fixup_find_links(opts['find_links'][1]) + links
  37. opts['find_links'] = ('setup', links)
  38. install_dir = dist.get_egg_cache_dir()
  39. cmd = easy_install(
  40. tmp_dist, args=["x"], install_dir=install_dir,
  41. exclude_scripts=True,
  42. always_copy=False, build_directory=None, editable=False,
  43. upgrade=False, multi_version=True, no_report=True, user=False
  44. )
  45. cmd.ensure_finalized()
  46. return cmd.easy_install(req)
  47. def fetch_build_egg(dist, req):
  48. """Fetch an egg needed for building.
  49. Use pip/wheel to fetch/build a wheel."""
  50. # Check pip is available.
  51. try:
  52. pkg_resources.get_distribution('pip')
  53. except pkg_resources.DistributionNotFound:
  54. dist.announce(
  55. 'WARNING: The pip package is not available, falling back '
  56. 'to EasyInstall for handling setup_requires/test_requires; '
  57. 'this is deprecated and will be removed in a future version.'
  58. , log.WARN
  59. )
  60. return _legacy_fetch_build_egg(dist, req)
  61. # Warn if wheel is not.
  62. try:
  63. pkg_resources.get_distribution('wheel')
  64. except pkg_resources.DistributionNotFound:
  65. dist.announce('WARNING: The wheel package is not available.', log.WARN)
  66. # Ignore environment markers; if supplied, it is required.
  67. req = strip_marker(req)
  68. # Take easy_install options into account, but do not override relevant
  69. # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
  70. # take precedence.
  71. opts = dist.get_option_dict('easy_install')
  72. if 'allow_hosts' in opts:
  73. raise DistutilsError('the `allow-hosts` option is not supported '
  74. 'when using pip to install requirements.')
  75. if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ:
  76. quiet = False
  77. else:
  78. quiet = True
  79. if 'PIP_INDEX_URL' in os.environ:
  80. index_url = None
  81. elif 'index_url' in opts:
  82. index_url = opts['index_url'][1]
  83. else:
  84. index_url = None
  85. if 'find_links' in opts:
  86. find_links = _fixup_find_links(opts['find_links'][1])[:]
  87. else:
  88. find_links = []
  89. if dist.dependency_links:
  90. find_links.extend(dist.dependency_links)
  91. eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
  92. environment = pkg_resources.Environment()
  93. for egg_dist in pkg_resources.find_distributions(eggs_dir):
  94. if egg_dist in req and environment.can_add(egg_dist):
  95. return egg_dist
  96. with TemporaryDirectory() as tmpdir:
  97. cmd = [
  98. sys.executable, '-m', 'pip',
  99. '--disable-pip-version-check',
  100. 'wheel', '--no-deps',
  101. '-w', tmpdir,
  102. ]
  103. if quiet:
  104. cmd.append('--quiet')
  105. if index_url is not None:
  106. cmd.extend(('--index-url', index_url))
  107. if find_links is not None:
  108. for link in find_links:
  109. cmd.extend(('--find-links', link))
  110. # If requirement is a PEP 508 direct URL, directly pass
  111. # the URL to pip, as `req @ url` does not work on the
  112. # command line.
  113. if req.url:
  114. cmd.append(req.url)
  115. else:
  116. cmd.append(str(req))
  117. try:
  118. subprocess.check_call(cmd)
  119. except subprocess.CalledProcessError as e:
  120. raise DistutilsError(str(e))
  121. wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
  122. dist_location = os.path.join(eggs_dir, wheel.egg_name())
  123. wheel.install_as_egg(dist_location)
  124. dist_metadata = pkg_resources.PathMetadata(
  125. dist_location, os.path.join(dist_location, 'EGG-INFO'))
  126. dist = pkg_resources.Distribution.from_filename(
  127. dist_location, metadata=dist_metadata)
  128. return dist
  129. def strip_marker(req):
  130. """
  131. Return a new requirement without the environment marker to avoid
  132. calling pip with something like `babel; extra == "i18n"`, which
  133. would always be ignored.
  134. """
  135. # create a copy to avoid mutating the input
  136. req = pkg_resources.Requirement.parse(str(req))
  137. req.marker = None
  138. return req