ctdb-daemon: Use ctdb_parse_node_address() in ctdbd
[samba4-gss.git] / third_party / waf / waflib / extras / msvcdeps.py
blobe8985bde7c7bafcb4d91c4c202c138bc1fe69a12
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Copyright Garmin International or its subsidiaries, 2012-2013
5 '''
6 Off-load dependency scanning from Python code to MSVC compiler
8 This tool is safe to load in any environment; it will only activate the
9 MSVC exploits when it finds that a particular taskgen uses MSVC to
10 compile.
12 Empirical testing shows about a 10% execution time savings from using
13 this tool as compared to c_preproc.
15 The technique of gutting scan() and pushing the dependency calculation
16 down to post_run() is cribbed from gccdeps.py.
18 This affects the cxx class, so make sure to load Qt5 after this tool.
20 Usage::
22 def options(opt):
23 opt.load('compiler_cxx')
24 def configure(conf):
25 conf.load('compiler_cxx msvcdeps')
26 '''
28 import os, sys, tempfile, threading
30 from waflib import Context, Errors, Logs, Task, Utils
31 from waflib.Tools import c_preproc, c, cxx, msvc
32 from waflib.TaskGen import feature, before_method
34 lock = threading.Lock()
36 PREPROCESSOR_FLAG = '/showIncludes'
37 INCLUDE_PATTERN = 'Note: including file:'
39 # Extensible by outside tools
40 supported_compilers = ['msvc']
42 @feature('c', 'cxx')
43 @before_method('process_source')
44 def apply_msvcdeps_flags(taskgen):
45 if taskgen.env.CC_NAME not in supported_compilers:
46 return
48 for flag in ('CFLAGS', 'CXXFLAGS'):
49 if taskgen.env.get_flat(flag).find(PREPROCESSOR_FLAG) < 0:
50 taskgen.env.append_value(flag, PREPROCESSOR_FLAG)
53 def get_correct_path_case(base_path, path):
54 '''
55 Return a case-corrected version of ``path`` by searching the filesystem for
56 ``path``, relative to ``base_path``, using the case returned by the filesystem.
57 '''
58 components = Utils.split_path(path)
60 corrected_path = ''
61 if os.path.isabs(path):
62 corrected_path = components.pop(0).upper() + os.sep
64 for part in components:
65 part = part.lower()
66 search_path = os.path.join(base_path, corrected_path)
67 if part == '..':
68 corrected_path = os.path.join(corrected_path, part)
69 search_path = os.path.normpath(search_path)
70 continue
72 for item in sorted(os.listdir(search_path)):
73 if item.lower() == part:
74 corrected_path = os.path.join(corrected_path, item)
75 break
76 else:
77 raise ValueError("Can't find %r in %r" % (part, search_path))
79 return corrected_path
82 def path_to_node(base_node, path, cached_nodes):
83 '''
84 Take the base node and the path and return a node
85 Results are cached because searching the node tree is expensive
86 The following code is executed by threads, it is not safe, so a lock is needed...
87 '''
88 # normalize the path to remove parent path components (..)
89 path = os.path.normpath(path)
91 # normalize the path case to increase likelihood of a cache hit
92 node_lookup_key = (base_node, os.path.normcase(path))
94 try:
95 node = cached_nodes[node_lookup_key]
96 except KeyError:
97 # retry with lock on cache miss
98 with lock:
99 try:
100 node = cached_nodes[node_lookup_key]
101 except KeyError:
102 path = get_correct_path_case(base_node.abspath(), path)
103 node = cached_nodes[node_lookup_key] = base_node.find_node(path)
105 return node
107 def post_run(self):
108 if self.env.CC_NAME not in supported_compilers:
109 return super(self.derived_msvcdeps, self).post_run()
111 # TODO this is unlikely to work with netcache
112 if getattr(self, 'cached', None):
113 return Task.Task.post_run(self)
115 resolved_nodes = []
116 unresolved_names = []
117 bld = self.generator.bld
119 # Dynamically bind to the cache
120 try:
121 cached_nodes = bld.cached_nodes
122 except AttributeError:
123 cached_nodes = bld.cached_nodes = {}
125 for path in self.msvcdeps_paths:
126 node = None
127 if os.path.isabs(path):
128 node = path_to_node(bld.root, path, cached_nodes)
129 else:
130 # when calling find_resource, make sure the path does not begin with '..'
131 base_node = bld.bldnode
132 path = [k for k in Utils.split_path(path) if k and k != '.']
133 while path[0] == '..':
134 path.pop(0)
135 base_node = base_node.parent
136 path = os.sep.join(path)
138 node = path_to_node(base_node, path, cached_nodes)
140 if not node:
141 raise ValueError('could not find %r for %r' % (path, self))
142 else:
143 if not c_preproc.go_absolute:
144 if not (node.is_child_of(bld.srcnode) or node.is_child_of(bld.bldnode)):
145 # System library
146 Logs.debug('msvcdeps: Ignoring system include %r', node)
147 continue
149 if id(node) == id(self.inputs[0]):
150 # ignore the source file, it is already in the dependencies
151 # this way, successful config tests may be retrieved from the cache
152 continue
154 resolved_nodes.append(node)
156 Logs.debug('deps: msvcdeps for %s returned %s', self, resolved_nodes)
158 bld.node_deps[self.uid()] = resolved_nodes
159 bld.raw_deps[self.uid()] = unresolved_names
161 try:
162 del self.cache_sig
163 except AttributeError:
164 pass
166 Task.Task.post_run(self)
168 def scan(self):
169 if self.env.CC_NAME not in supported_compilers:
170 return super(self.derived_msvcdeps, self).scan()
172 resolved_nodes = self.generator.bld.node_deps.get(self.uid(), [])
173 unresolved_names = []
174 return (resolved_nodes, unresolved_names)
176 def sig_implicit_deps(self):
177 if self.env.CC_NAME not in supported_compilers:
178 return super(self.derived_msvcdeps, self).sig_implicit_deps()
179 bld = self.generator.bld
181 try:
182 return self.compute_sig_implicit_deps()
183 except Errors.TaskNotReady:
184 raise ValueError("Please specify the build order precisely with msvcdeps (c/c++ tasks)")
185 except EnvironmentError:
186 # If a file is renamed, assume the dependencies are stale and must be recalculated
187 for x in bld.node_deps.get(self.uid(), []):
188 if not x.is_bld() and not x.exists():
189 try:
190 del x.parent.children[x.name]
191 except KeyError:
192 pass
194 key = self.uid()
195 bld.node_deps[key] = []
196 bld.raw_deps[key] = []
197 return Utils.SIG_NIL
199 def exec_command(self, cmd, **kw):
200 if self.env.CC_NAME not in supported_compilers:
201 return super(self.derived_msvcdeps, self).exec_command(cmd, **kw)
203 if not 'cwd' in kw:
204 kw['cwd'] = self.get_cwd()
206 if self.env.PATH:
207 env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
208 env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
210 # The Visual Studio IDE adds an environment variable that causes
211 # the MS compiler to send its textual output directly to the
212 # debugging window rather than normal stdout/stderr.
214 # This is unrecoverably bad for this tool because it will cause
215 # all the dependency scanning to see an empty stdout stream and
216 # assume that the file being compiled uses no headers.
218 # See http://blogs.msdn.com/b/freik/archive/2006/04/05/569025.aspx
220 # Attempting to repair the situation by deleting the offending
221 # envvar at this point in tool execution will not be good enough--
222 # its presence poisons the 'waf configure' step earlier. We just
223 # want to put a sanity check here in order to help developers
224 # quickly diagnose the issue if an otherwise-good Waf tree
225 # is then executed inside the MSVS IDE.
226 assert 'VS_UNICODE_OUTPUT' not in kw['env']
228 cmd, args = self.split_argfile(cmd)
229 try:
230 (fd, tmp) = tempfile.mkstemp()
231 os.write(fd, '\r\n'.join(args).encode())
232 os.close(fd)
234 self.msvcdeps_paths = []
235 kw['env'] = kw.get('env', os.environ.copy())
236 kw['cwd'] = kw.get('cwd', os.getcwd())
237 kw['quiet'] = Context.STDOUT
238 kw['output'] = Context.STDOUT
240 out = []
241 if Logs.verbose:
242 Logs.debug('argfile: @%r -> %r', tmp, args)
243 try:
244 raw_out = self.generator.bld.cmd_and_log(cmd + ['@' + tmp], **kw)
245 ret = 0
246 except Errors.WafError as e:
247 # Use e.msg if e.stdout is not set
248 raw_out = getattr(e, 'stdout', e.msg)
250 # Return non-zero error code even if we didn't
251 # get one from the exception object
252 ret = getattr(e, 'returncode', 1)
254 Logs.debug('msvcdeps: Running for: %s' % self.inputs[0])
255 for line in raw_out.splitlines():
256 if line.startswith(INCLUDE_PATTERN):
257 # Only strip whitespace after log to preserve
258 # dependency structure in debug output
259 inc_path = line[len(INCLUDE_PATTERN):]
260 Logs.debug('msvcdeps: Regex matched %s', inc_path)
261 self.msvcdeps_paths.append(inc_path.strip())
262 else:
263 out.append(line)
265 # Pipe through the remaining stdout content (not related to /showIncludes)
266 if self.generator.bld.logger:
267 self.generator.bld.logger.debug('out: %s' % os.linesep.join(out))
268 else:
269 sys.stdout.write(os.linesep.join(out) + os.linesep)
271 return ret
272 finally:
273 try:
274 os.remove(tmp)
275 except OSError:
276 # anti-virus and indexers can keep files open -_-
277 pass
280 def wrap_compiled_task(classname):
281 derived_class = type(classname, (Task.classes[classname],), {})
282 derived_class.derived_msvcdeps = derived_class
283 derived_class.post_run = post_run
284 derived_class.scan = scan
285 derived_class.sig_implicit_deps = sig_implicit_deps
286 derived_class.exec_command = exec_command
288 for k in ('c', 'cxx'):
289 if k in Task.classes:
290 wrap_compiled_task(k)
292 def options(opt):
293 raise ValueError('Do not load msvcdeps options')