win32_opts.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #! /usr/bin/env python
  2. # encoding: utf-8
  3. """
  4. Windows-specific optimizations
  5. This module can help reducing the overhead of listing files on windows
  6. (more than 10000 files). Python 3.5 already provides the listdir
  7. optimization though.
  8. """
  9. import os
  10. from waflib import Utils, Build, Node, Logs
  11. try:
  12. TP = '%s\\*'.decode('ascii')
  13. except AttributeError:
  14. TP = '%s\\*'
  15. if Utils.is_win32:
  16. from waflib.Tools import md5_tstamp
  17. import ctypes, ctypes.wintypes
  18. FindFirstFile = ctypes.windll.kernel32.FindFirstFileW
  19. FindNextFile = ctypes.windll.kernel32.FindNextFileW
  20. FindClose = ctypes.windll.kernel32.FindClose
  21. FILE_ATTRIBUTE_DIRECTORY = 0x10
  22. INVALID_HANDLE_VALUE = -1
  23. UPPER_FOLDERS = ('.', '..')
  24. try:
  25. UPPER_FOLDERS = [unicode(x) for x in UPPER_FOLDERS]
  26. except NameError:
  27. pass
  28. def cached_hash_file(self):
  29. try:
  30. cache = self.ctx.cache_listdir_cache_hash_file
  31. except AttributeError:
  32. cache = self.ctx.cache_listdir_cache_hash_file = {}
  33. if id(self.parent) in cache:
  34. try:
  35. t = cache[id(self.parent)][self.name]
  36. except KeyError:
  37. raise IOError('Not a file')
  38. else:
  39. # an opportunity to list the files and the timestamps at once
  40. findData = ctypes.wintypes.WIN32_FIND_DATAW()
  41. find = FindFirstFile(TP % self.parent.abspath(), ctypes.byref(findData))
  42. if find == INVALID_HANDLE_VALUE:
  43. cache[id(self.parent)] = {}
  44. raise IOError('Not a file')
  45. cache[id(self.parent)] = lst_files = {}
  46. try:
  47. while True:
  48. if findData.cFileName not in UPPER_FOLDERS:
  49. thatsadir = findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
  50. if not thatsadir:
  51. ts = findData.ftLastWriteTime
  52. d = (ts.dwLowDateTime << 32) | ts.dwHighDateTime
  53. lst_files[str(findData.cFileName)] = d
  54. if not FindNextFile(find, ctypes.byref(findData)):
  55. break
  56. except Exception:
  57. cache[id(self.parent)] = {}
  58. raise IOError('Not a file')
  59. finally:
  60. FindClose(find)
  61. t = lst_files[self.name]
  62. fname = self.abspath()
  63. if fname in Build.hashes_md5_tstamp:
  64. if Build.hashes_md5_tstamp[fname][0] == t:
  65. return Build.hashes_md5_tstamp[fname][1]
  66. try:
  67. fd = os.open(fname, os.O_BINARY | os.O_RDONLY | os.O_NOINHERIT)
  68. except OSError:
  69. raise IOError('Cannot read from %r' % fname)
  70. f = os.fdopen(fd, 'rb')
  71. m = Utils.md5()
  72. rb = 1
  73. try:
  74. while rb:
  75. rb = f.read(200000)
  76. m.update(rb)
  77. finally:
  78. f.close()
  79. # ensure that the cache is overwritten
  80. Build.hashes_md5_tstamp[fname] = (t, m.digest())
  81. return m.digest()
  82. Node.Node.cached_hash_file = cached_hash_file
  83. def get_bld_sig_win32(self):
  84. try:
  85. return self.ctx.hash_cache[id(self)]
  86. except KeyError:
  87. pass
  88. except AttributeError:
  89. self.ctx.hash_cache = {}
  90. self.ctx.hash_cache[id(self)] = ret = Utils.h_file(self.abspath())
  91. return ret
  92. Node.Node.get_bld_sig = get_bld_sig_win32
  93. def isfile_cached(self):
  94. # optimize for nt.stat calls, assuming there are many files for few folders
  95. try:
  96. cache = self.__class__.cache_isfile_cache
  97. except AttributeError:
  98. cache = self.__class__.cache_isfile_cache = {}
  99. try:
  100. c1 = cache[id(self.parent)]
  101. except KeyError:
  102. c1 = cache[id(self.parent)] = []
  103. curpath = self.parent.abspath()
  104. findData = ctypes.wintypes.WIN32_FIND_DATAW()
  105. find = FindFirstFile(TP % curpath, ctypes.byref(findData))
  106. if find == INVALID_HANDLE_VALUE:
  107. Logs.error("invalid win32 handle isfile_cached %r", self.abspath())
  108. return os.path.isfile(self.abspath())
  109. try:
  110. while True:
  111. if findData.cFileName not in UPPER_FOLDERS:
  112. thatsadir = findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
  113. if not thatsadir:
  114. c1.append(str(findData.cFileName))
  115. if not FindNextFile(find, ctypes.byref(findData)):
  116. break
  117. except Exception as e:
  118. Logs.error('exception while listing a folder %r %r', self.abspath(), e)
  119. return os.path.isfile(self.abspath())
  120. finally:
  121. FindClose(find)
  122. return self.name in c1
  123. Node.Node.isfile_cached = isfile_cached
  124. def find_or_declare_win32(self, lst):
  125. # assuming that "find_or_declare" is called before the build starts, remove the calls to os.path.isfile
  126. if isinstance(lst, str):
  127. lst = [x for x in Utils.split_path(lst) if x and x != '.']
  128. node = self.get_bld().search_node(lst)
  129. if node:
  130. if not node.isfile_cached():
  131. try:
  132. node.parent.mkdir()
  133. except OSError:
  134. pass
  135. return node
  136. self = self.get_src()
  137. node = self.find_node(lst)
  138. if node:
  139. if not node.isfile_cached():
  140. try:
  141. node.parent.mkdir()
  142. except OSError:
  143. pass
  144. return node
  145. node = self.get_bld().make_node(lst)
  146. node.parent.mkdir()
  147. return node
  148. Node.Node.find_or_declare = find_or_declare_win32