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.

req_tracker.py 2.8KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. from __future__ import absolute_import
  2. import contextlib
  3. import errno
  4. import hashlib
  5. import logging
  6. import os
  7. from pip._internal.utils.temp_dir import TempDirectory
  8. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  9. if MYPY_CHECK_RUNNING:
  10. from typing import Set, Iterator # noqa: F401
  11. from pip._internal.req.req_install import InstallRequirement # noqa: F401
  12. from pip._internal.models.link import Link # noqa: F401
  13. logger = logging.getLogger(__name__)
  14. class RequirementTracker(object):
  15. def __init__(self):
  16. # type: () -> None
  17. self._root = os.environ.get('PIP_REQ_TRACKER')
  18. if self._root is None:
  19. self._temp_dir = TempDirectory(delete=False, kind='req-tracker')
  20. self._temp_dir.create()
  21. self._root = os.environ['PIP_REQ_TRACKER'] = self._temp_dir.path
  22. logger.debug('Created requirements tracker %r', self._root)
  23. else:
  24. self._temp_dir = None
  25. logger.debug('Re-using requirements tracker %r', self._root)
  26. self._entries = set() # type: Set[InstallRequirement]
  27. def __enter__(self):
  28. return self
  29. def __exit__(self, exc_type, exc_val, exc_tb):
  30. self.cleanup()
  31. def _entry_path(self, link):
  32. # type: (Link) -> str
  33. hashed = hashlib.sha224(link.url_without_fragment.encode()).hexdigest()
  34. return os.path.join(self._root, hashed)
  35. def add(self, req):
  36. # type: (InstallRequirement) -> None
  37. link = req.link
  38. info = str(req)
  39. entry_path = self._entry_path(link)
  40. try:
  41. with open(entry_path) as fp:
  42. # Error, these's already a build in progress.
  43. raise LookupError('%s is already being built: %s'
  44. % (link, fp.read()))
  45. except IOError as e:
  46. if e.errno != errno.ENOENT:
  47. raise
  48. assert req not in self._entries
  49. with open(entry_path, 'w') as fp:
  50. fp.write(info)
  51. self._entries.add(req)
  52. logger.debug('Added %s to build tracker %r', req, self._root)
  53. def remove(self, req):
  54. # type: (InstallRequirement) -> None
  55. link = req.link
  56. self._entries.remove(req)
  57. os.unlink(self._entry_path(link))
  58. logger.debug('Removed %s from build tracker %r', req, self._root)
  59. def cleanup(self):
  60. # type: () -> None
  61. for req in set(self._entries):
  62. self.remove(req)
  63. remove = self._temp_dir is not None
  64. if remove:
  65. self._temp_dir.cleanup()
  66. logger.debug('%s build tracker %r',
  67. 'Removed' if remove else 'Cleaned',
  68. self._root)
  69. @contextlib.contextmanager
  70. def track(self, req):
  71. # type: (InstallRequirement) -> Iterator[None]
  72. self.add(req)
  73. yield
  74. self.remove(req)