5 Windows-specific optimizations
7 This module can help reducing the overhead of listing files on windows
8 (more than 10000 files). Python 3.5 already provides the listdir
13 from waflib
import Utils
, Build
, Node
, Logs
16 TP
= '%s\\*'.decode('ascii')
17 except AttributeError:
21 from waflib
.Tools
import md5_tstamp
22 import ctypes
, ctypes
.wintypes
24 FindFirstFile
= ctypes
.windll
.kernel32
.FindFirstFileW
25 FindNextFile
= ctypes
.windll
.kernel32
.FindNextFileW
26 FindClose
= ctypes
.windll
.kernel32
.FindClose
27 FILE_ATTRIBUTE_DIRECTORY
= 0x10
28 INVALID_HANDLE_VALUE
= -1
29 UPPER_FOLDERS
= ('.', '..')
31 UPPER_FOLDERS
= [unicode(x
) for x
in UPPER_FOLDERS
]
35 def cached_hash_file(self
):
37 cache
= self
.ctx
.cache_listdir_cache_hash_file
38 except AttributeError:
39 cache
= self
.ctx
.cache_listdir_cache_hash_file
= {}
41 if id(self
.parent
) in cache
:
43 t
= cache
[id(self
.parent
)][self
.name
]
45 raise IOError('Not a file')
47 # an opportunity to list the files and the timestamps at once
48 findData
= ctypes
.wintypes
.WIN32_FIND_DATAW()
49 find
= FindFirstFile(TP
% self
.parent
.abspath(), ctypes
.byref(findData
))
51 if find
== INVALID_HANDLE_VALUE
:
52 cache
[id(self
.parent
)] = {}
53 raise IOError('Not a file')
55 cache
[id(self
.parent
)] = lst_files
= {}
58 if findData
.cFileName
not in UPPER_FOLDERS
:
59 thatsadir
= findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
61 ts
= findData
.ftLastWriteTime
62 d
= (ts
.dwLowDateTime
<< 32) | ts
.dwHighDateTime
63 lst_files
[str(findData
.cFileName
)] = d
64 if not FindNextFile(find
, ctypes
.byref(findData
)):
67 cache
[id(self
.parent
)] = {}
68 raise IOError('Not a file')
71 t
= lst_files
[self
.name
]
73 fname
= self
.abspath()
74 if fname
in Build
.hashes_md5_tstamp
:
75 if Build
.hashes_md5_tstamp
[fname
][0] == t
:
76 return Build
.hashes_md5_tstamp
[fname
][1]
79 fd
= os
.open(fname
, os
.O_BINARY | os
.O_RDONLY | os
.O_NOINHERIT
)
81 raise IOError('Cannot read from %r' % fname
)
82 f
= os
.fdopen(fd
, 'rb')
92 # ensure that the cache is overwritten
93 Build
.hashes_md5_tstamp
[fname
] = (t
, m
.digest())
95 Node
.Node
.cached_hash_file
= cached_hash_file
97 def get_bld_sig_win32(self
):
99 return self
.ctx
.hash_cache
[id(self
)]
102 except AttributeError:
103 self
.ctx
.hash_cache
= {}
104 self
.ctx
.hash_cache
[id(self
)] = ret
= Utils
.h_file(self
.abspath())
106 Node
.Node
.get_bld_sig
= get_bld_sig_win32
108 def isfile_cached(self
):
109 # optimize for nt.stat calls, assuming there are many files for few folders
111 cache
= self
.__class
__.cache_isfile_cache
112 except AttributeError:
113 cache
= self
.__class
__.cache_isfile_cache
= {}
116 c1
= cache
[id(self
.parent
)]
118 c1
= cache
[id(self
.parent
)] = []
120 curpath
= self
.parent
.abspath()
121 findData
= ctypes
.wintypes
.WIN32_FIND_DATAW()
122 find
= FindFirstFile(TP
% curpath
, ctypes
.byref(findData
))
124 if find
== INVALID_HANDLE_VALUE
:
125 Logs
.error("invalid win32 handle isfile_cached %r", self
.abspath())
126 return os
.path
.isfile(self
.abspath())
130 if findData
.cFileName
not in UPPER_FOLDERS
:
131 thatsadir
= findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
133 c1
.append(str(findData
.cFileName
))
134 if not FindNextFile(find
, ctypes
.byref(findData
)):
136 except Exception as e
:
137 Logs
.error('exception while listing a folder %r %r', self
.abspath(), e
)
138 return os
.path
.isfile(self
.abspath())
141 return self
.name
in c1
142 Node
.Node
.isfile_cached
= isfile_cached
144 def find_or_declare_win32(self
, lst
):
145 # assuming that "find_or_declare" is called before the build starts, remove the calls to os.path.isfile
146 if isinstance(lst
, str):
147 lst
= [x
for x
in Utils
.split_path(lst
) if x
and x
!= '.']
149 node
= self
.get_bld().search_node(lst
)
151 if not node
.isfile_cached():
157 self
= self
.get_src()
158 node
= self
.find_node(lst
)
160 if not node
.isfile_cached():
166 node
= self
.get_bld().make_node(lst
)
169 Node
.Node
.find_or_declare
= find_or_declare_win32