installer.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import glob
  2. import os
  3. import subprocess
  4. import sys
  5. import tempfile
  6. from distutils import log
  7. from distutils.errors import DistutilsError
  8. from functools import partial
  9. from . import _reqs
  10. from .wheel import Wheel
  11. from .warnings import SetuptoolsDeprecationWarning
  12. def _fixup_find_links(find_links):
  13. """Ensure find-links option end-up being a list of strings."""
  14. if isinstance(find_links, str):
  15. return find_links.split()
  16. assert isinstance(find_links, (tuple, list))
  17. return find_links
  18. def fetch_build_egg(dist, req):
  19. """Fetch an egg needed for building.
  20. Use pip/wheel to fetch/build a wheel."""
  21. _DeprecatedInstaller.emit()
  22. _warn_wheel_not_available(dist)
  23. return _fetch_build_egg_no_warn(dist, req)
  24. def _fetch_build_eggs(dist, requires):
  25. import pkg_resources # Delay import to avoid unnecessary side-effects
  26. _DeprecatedInstaller.emit(stacklevel=3)
  27. _warn_wheel_not_available(dist)
  28. resolved_dists = pkg_resources.working_set.resolve(
  29. _reqs.parse(requires, pkg_resources.Requirement), # required for compatibility
  30. installer=partial(_fetch_build_egg_no_warn, dist), # avoid warning twice
  31. replace_conflicting=True,
  32. )
  33. for dist in resolved_dists:
  34. pkg_resources.working_set.add(dist, replace=True)
  35. return resolved_dists
  36. def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex (16) # FIXME
  37. import pkg_resources # Delay import to avoid unnecessary side-effects
  38. # Ignore environment markers; if supplied, it is required.
  39. req = strip_marker(req)
  40. # Take easy_install options into account, but do not override relevant
  41. # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
  42. # take precedence.
  43. opts = dist.get_option_dict('easy_install')
  44. if 'allow_hosts' in opts:
  45. raise DistutilsError(
  46. 'the `allow-hosts` option is not supported '
  47. 'when using pip to install requirements.'
  48. )
  49. quiet = 'PIP_QUIET' not in os.environ and 'PIP_VERBOSE' not in os.environ
  50. if 'PIP_INDEX_URL' in os.environ:
  51. index_url = None
  52. elif 'index_url' in opts:
  53. index_url = opts['index_url'][1]
  54. else:
  55. index_url = None
  56. find_links = (
  57. _fixup_find_links(opts['find_links'][1])[:] if 'find_links' in opts else []
  58. )
  59. if dist.dependency_links:
  60. find_links.extend(dist.dependency_links)
  61. eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
  62. environment = pkg_resources.Environment()
  63. for egg_dist in pkg_resources.find_distributions(eggs_dir):
  64. if egg_dist in req and environment.can_add(egg_dist):
  65. return egg_dist
  66. with tempfile.TemporaryDirectory() as tmpdir:
  67. cmd = [
  68. sys.executable,
  69. '-m',
  70. 'pip',
  71. '--disable-pip-version-check',
  72. 'wheel',
  73. '--no-deps',
  74. '-w',
  75. tmpdir,
  76. ]
  77. if quiet:
  78. cmd.append('--quiet')
  79. if index_url is not None:
  80. cmd.extend(('--index-url', index_url))
  81. for link in find_links or []:
  82. cmd.extend(('--find-links', link))
  83. # If requirement is a PEP 508 direct URL, directly pass
  84. # the URL to pip, as `req @ url` does not work on the
  85. # command line.
  86. cmd.append(req.url or str(req))
  87. try:
  88. subprocess.check_call(cmd)
  89. except subprocess.CalledProcessError as e:
  90. raise DistutilsError(str(e)) from e
  91. wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
  92. dist_location = os.path.join(eggs_dir, wheel.egg_name())
  93. wheel.install_as_egg(dist_location)
  94. dist_metadata = pkg_resources.PathMetadata(
  95. dist_location, os.path.join(dist_location, 'EGG-INFO')
  96. )
  97. dist = pkg_resources.Distribution.from_filename(
  98. dist_location, metadata=dist_metadata
  99. )
  100. return dist
  101. def strip_marker(req):
  102. """
  103. Return a new requirement without the environment marker to avoid
  104. calling pip with something like `babel; extra == "i18n"`, which
  105. would always be ignored.
  106. """
  107. import pkg_resources # Delay import to avoid unnecessary side-effects
  108. # create a copy to avoid mutating the input
  109. req = pkg_resources.Requirement.parse(str(req))
  110. req.marker = None
  111. return req
  112. def _warn_wheel_not_available(dist):
  113. import pkg_resources # Delay import to avoid unnecessary side-effects
  114. try:
  115. pkg_resources.get_distribution('wheel')
  116. except pkg_resources.DistributionNotFound:
  117. dist.announce('WARNING: The wheel package is not available.', log.WARN)
  118. class _DeprecatedInstaller(SetuptoolsDeprecationWarning):
  119. _SUMMARY = "setuptools.installer and fetch_build_eggs are deprecated."
  120. _DETAILS = """
  121. Requirements should be satisfied by a PEP 517 installer.
  122. If you are using pip, you can try `pip install --use-pep517`.
  123. """
  124. # _DUE_DATE not decided yet