req_tracker.py 3.1 KB

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