req_set.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from __future__ import absolute_import
  2. import logging
  3. from collections import OrderedDict
  4. from pip._internal.exceptions import InstallationError
  5. from pip._internal.utils.logging import indent_log
  6. from pip._internal.wheel import Wheel
  7. logger = logging.getLogger(__name__)
  8. class RequirementSet(object):
  9. def __init__(self, require_hashes=False):
  10. """Create a RequirementSet.
  11. :param wheel_cache: The pip wheel cache, for passing to
  12. InstallRequirement.
  13. """
  14. self.requirements = OrderedDict()
  15. self.require_hashes = require_hashes
  16. # Mapping of alias: real_name
  17. self.requirement_aliases = {}
  18. self.unnamed_requirements = []
  19. self.successfully_downloaded = []
  20. self.reqs_to_cleanup = []
  21. def __str__(self):
  22. reqs = [req for req in self.requirements.values()
  23. if not req.comes_from]
  24. reqs.sort(key=lambda req: req.name.lower())
  25. return ' '.join([str(req.req) for req in reqs])
  26. def __repr__(self):
  27. reqs = [req for req in self.requirements.values()]
  28. reqs.sort(key=lambda req: req.name.lower())
  29. reqs_str = ', '.join([str(req.req) for req in reqs])
  30. return ('<%s object; %d requirement(s): %s>'
  31. % (self.__class__.__name__, len(reqs), reqs_str))
  32. def add_requirement(self, install_req, parent_req_name=None,
  33. extras_requested=None):
  34. """Add install_req as a requirement to install.
  35. :param parent_req_name: The name of the requirement that needed this
  36. added. The name is used because when multiple unnamed requirements
  37. resolve to the same name, we could otherwise end up with dependency
  38. links that point outside the Requirements set. parent_req must
  39. already be added. Note that None implies that this is a user
  40. supplied requirement, vs an inferred one.
  41. :param extras_requested: an iterable of extras used to evaluate the
  42. environment markers.
  43. :return: Additional requirements to scan. That is either [] if
  44. the requirement is not applicable, or [install_req] if the
  45. requirement is applicable and has just been added.
  46. """
  47. name = install_req.name
  48. if not install_req.match_markers(extras_requested):
  49. logger.info("Ignoring %s: markers '%s' don't match your "
  50. "environment", install_req.name,
  51. install_req.markers)
  52. return [], None
  53. # This check has to come after we filter requirements with the
  54. # environment markers.
  55. if install_req.link and install_req.link.is_wheel:
  56. wheel = Wheel(install_req.link.filename)
  57. if not wheel.supported():
  58. raise InstallationError(
  59. "%s is not a supported wheel on this platform." %
  60. wheel.filename
  61. )
  62. # This next bit is really a sanity check.
  63. assert install_req.is_direct == (parent_req_name is None), (
  64. "a direct req shouldn't have a parent and also, "
  65. "a non direct req should have a parent"
  66. )
  67. if not name:
  68. # url or path requirement w/o an egg fragment
  69. self.unnamed_requirements.append(install_req)
  70. return [install_req], None
  71. else:
  72. try:
  73. existing_req = self.get_requirement(name)
  74. except KeyError:
  75. existing_req = None
  76. if (parent_req_name is None and existing_req and not
  77. existing_req.constraint and
  78. existing_req.extras == install_req.extras and not
  79. existing_req.req.specifier == install_req.req.specifier):
  80. raise InstallationError(
  81. 'Double requirement given: %s (already in %s, name=%r)'
  82. % (install_req, existing_req, name))
  83. if not existing_req:
  84. # Add requirement
  85. self.requirements[name] = install_req
  86. # FIXME: what about other normalizations? E.g., _ vs. -?
  87. if name.lower() != name:
  88. self.requirement_aliases[name.lower()] = name
  89. result = [install_req]
  90. else:
  91. # Assume there's no need to scan, and that we've already
  92. # encountered this for scanning.
  93. result = []
  94. if not install_req.constraint and existing_req.constraint:
  95. if (install_req.link and not (existing_req.link and
  96. install_req.link.path == existing_req.link.path)):
  97. self.reqs_to_cleanup.append(install_req)
  98. raise InstallationError(
  99. "Could not satisfy constraints for '%s': "
  100. "installation from path or url cannot be "
  101. "constrained to a version" % name,
  102. )
  103. # If we're now installing a constraint, mark the existing
  104. # object for real installation.
  105. existing_req.constraint = False
  106. existing_req.extras = tuple(
  107. sorted(set(existing_req.extras).union(
  108. set(install_req.extras))))
  109. logger.debug("Setting %s extras to: %s",
  110. existing_req, existing_req.extras)
  111. # And now we need to scan this.
  112. result = [existing_req]
  113. # Canonicalise to the already-added object for the backref
  114. # check below.
  115. install_req = existing_req
  116. # We return install_req here to allow for the caller to add it to
  117. # the dependency information for the parent package.
  118. return result, install_req
  119. def has_requirement(self, project_name):
  120. name = project_name.lower()
  121. if (name in self.requirements and
  122. not self.requirements[name].constraint or
  123. name in self.requirement_aliases and
  124. not self.requirements[self.requirement_aliases[name]].constraint):
  125. return True
  126. return False
  127. @property
  128. def has_requirements(self):
  129. return list(req for req in self.requirements.values() if not
  130. req.constraint) or self.unnamed_requirements
  131. def get_requirement(self, project_name):
  132. for name in project_name, project_name.lower():
  133. if name in self.requirements:
  134. return self.requirements[name]
  135. if name in self.requirement_aliases:
  136. return self.requirements[self.requirement_aliases[name]]
  137. raise KeyError("No project with the name %r" % project_name)
  138. def cleanup_files(self):
  139. """Clean up files, remove builds."""
  140. logger.debug('Cleaning up...')
  141. with indent_log():
  142. for req in self.reqs_to_cleanup:
  143. req.remove_temporary_source()