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.

temp_dir.py 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from __future__ import absolute_import
  2. import errno
  3. import itertools
  4. import logging
  5. import os.path
  6. import tempfile
  7. from pip._internal.utils.misc import rmtree
  8. logger = logging.getLogger(__name__)
  9. class TempDirectory(object):
  10. """Helper class that owns and cleans up a temporary directory.
  11. This class can be used as a context manager or as an OO representation of a
  12. temporary directory.
  13. Attributes:
  14. path
  15. Location to the created temporary directory or None
  16. delete
  17. Whether the directory should be deleted when exiting
  18. (when used as a contextmanager)
  19. Methods:
  20. create()
  21. Creates a temporary directory and stores its path in the path
  22. attribute.
  23. cleanup()
  24. Deletes the temporary directory and sets path attribute to None
  25. When used as a context manager, a temporary directory is created on
  26. entering the context and, if the delete attribute is True, on exiting the
  27. context the created directory is deleted.
  28. """
  29. def __init__(self, path=None, delete=None, kind="temp"):
  30. super(TempDirectory, self).__init__()
  31. if path is None and delete is None:
  32. # If we were not given an explicit directory, and we were not given
  33. # an explicit delete option, then we'll default to deleting.
  34. delete = True
  35. self.path = path
  36. self.delete = delete
  37. self.kind = kind
  38. def __repr__(self):
  39. return "<{} {!r}>".format(self.__class__.__name__, self.path)
  40. def __enter__(self):
  41. self.create()
  42. return self
  43. def __exit__(self, exc, value, tb):
  44. if self.delete:
  45. self.cleanup()
  46. def create(self):
  47. """Create a temporary directory and store its path in self.path
  48. """
  49. if self.path is not None:
  50. logger.debug(
  51. "Skipped creation of temporary directory: {}".format(self.path)
  52. )
  53. return
  54. # We realpath here because some systems have their default tmpdir
  55. # symlinked to another directory. This tends to confuse build
  56. # scripts, so we canonicalize the path by traversing potential
  57. # symlinks here.
  58. self.path = os.path.realpath(
  59. tempfile.mkdtemp(prefix="pip-{}-".format(self.kind))
  60. )
  61. logger.debug("Created temporary directory: {}".format(self.path))
  62. def cleanup(self):
  63. """Remove the temporary directory created and reset state
  64. """
  65. if self.path is not None and os.path.exists(self.path):
  66. rmtree(self.path)
  67. self.path = None
  68. class AdjacentTempDirectory(TempDirectory):
  69. """Helper class that creates a temporary directory adjacent to a real one.
  70. Attributes:
  71. original
  72. The original directory to create a temp directory for.
  73. path
  74. After calling create() or entering, contains the full
  75. path to the temporary directory.
  76. delete
  77. Whether the directory should be deleted when exiting
  78. (when used as a contextmanager)
  79. """
  80. # The characters that may be used to name the temp directory
  81. # We always prepend a ~ and then rotate through these until
  82. # a usable name is found.
  83. # pkg_resources raises a different error for .dist-info folder
  84. # with leading '-' and invalid metadata
  85. LEADING_CHARS = "-~.=%0123456789"
  86. def __init__(self, original, delete=None):
  87. super(AdjacentTempDirectory, self).__init__(delete=delete)
  88. self.original = original.rstrip('/\\')
  89. @classmethod
  90. def _generate_names(cls, name):
  91. """Generates a series of temporary names.
  92. The algorithm replaces the leading characters in the name
  93. with ones that are valid filesystem characters, but are not
  94. valid package names (for both Python and pip definitions of
  95. package).
  96. """
  97. for i in range(1, len(name)):
  98. for candidate in itertools.combinations_with_replacement(
  99. cls.LEADING_CHARS, i - 1):
  100. new_name = '~' + ''.join(candidate) + name[i:]
  101. if new_name != name:
  102. yield new_name
  103. # If we make it this far, we will have to make a longer name
  104. for i in range(len(cls.LEADING_CHARS)):
  105. for candidate in itertools.combinations_with_replacement(
  106. cls.LEADING_CHARS, i):
  107. new_name = '~' + ''.join(candidate) + name
  108. if new_name != name:
  109. yield new_name
  110. def create(self):
  111. root, name = os.path.split(self.original)
  112. for candidate in self._generate_names(name):
  113. path = os.path.join(root, candidate)
  114. try:
  115. os.mkdir(path)
  116. except OSError as ex:
  117. # Continue if the name exists already
  118. if ex.errno != errno.EEXIST:
  119. raise
  120. else:
  121. self.path = os.path.realpath(path)
  122. break
  123. if not self.path:
  124. # Final fallback on the default behavior.
  125. self.path = os.path.realpath(
  126. tempfile.mkdtemp(prefix="pip-{}-".format(self.kind))
  127. )
  128. logger.debug("Created temporary directory: {}".format(self.path))