3 # Thomas Nagy, 2015 (ita)
6 Execute tasks through strace to obtain dependencies after the process is run. This
7 scheme is similar to that of the Fabricate script.
15 * This will not work when advanced scanners are needed (qt4/qt5)
16 * The overhead of running 'strace' is significant (56s -> 1m29s)
17 * It will not work on Windows :-)
20 import os
, re
, threading
21 from waflib
import Task
, Logs
, Utils
23 #TRACECALLS = 'trace=access,chdir,clone,creat,execve,exit_group,fork,lstat,lstat64,mkdir,open,rename,stat,stat64,symlink,vfork'
24 TRACECALLS
= 'trace=process,file'
26 BANNED
= ('/tmp', '/proc', '/sys', '/dev')
28 s_process
= r
'(?:clone|fork|vfork)\(.*?(?P<npid>\d+)'
29 s_file
= r
'(?P<call>\w+)\("(?P<path>([^"\\]|\\.)*)"(.*)'
30 re_lines
= re
.compile(r
'^(?P<pid>\d+)\s+(?:(?:%s)|(?:%s))\r*$' % (s_file
, s_process
), re
.IGNORECASE | re
.MULTILINE
)
31 strace_lock
= threading
.Lock()
34 conf
.find_program('strace')
36 def task_method(func
):
37 # Decorator function to bind/replace methods on the base Task class
39 # The methods Task.exec_command and Task.sig_implicit_deps already exists and are rarely overridden
40 # we thus expect that we are the only ones doing this
42 setattr(Task
.Task
, 'nostrace_%s' % func
.__name
__, getattr(Task
.Task
, func
.__name
__))
43 except AttributeError:
45 setattr(Task
.Task
, func
.__name
__, func
)
49 def get_strace_file(self
):
51 return self
.strace_file
52 except AttributeError:
56 ret
= self
.outputs
[0].abspath() + '.strace'
58 ret
= '%s%s%d%s' % (self
.generator
.bld
.bldnode
.abspath(), os
.sep
, id(self
), '.strace')
59 self
.strace_file
= ret
63 def get_strace_args(self
):
64 return (self
.env
.STRACE
or ['strace']) + ['-e', TRACECALLS
, '-f', '-o', self
.get_strace_file()]
67 def exec_command(self
, cmd
, **kw
):
68 bld
= self
.generator
.bld
70 kw
['cwd'] = self
.get_cwd()
72 args
= self
.get_strace_args()
73 fname
= self
.get_strace_file()
74 if isinstance(cmd
, list):
77 cmd
= '%s %s' % (' '.join(args
), cmd
)
80 ret
= bld
.exec_command(cmd
, **kw
)
83 self
.parse_strace_deps(fname
, kw
['cwd'])
87 def sig_implicit_deps(self
):
88 # bypass the scanner functions
92 def parse_strace_deps(self
, path
, cwd
):
93 # uncomment the following line to disable the dependencies and force a file scan
96 cnt
= Utils
.readf(path
)
103 if not isinstance(cwd
, str):
107 bld
= self
.generator
.bld
109 cache
= bld
.strace_cache
110 except AttributeError:
111 cache
= bld
.strace_cache
= {}
113 # chdir and relative paths
118 for m
in re
.finditer(re_lines
, cnt
):
119 # scraping the output of strace
122 npid
= m
.group('npid')
123 pid_to_cwd
[npid
] = pid_to_cwd
.get(pid
, cwd
)
126 p
= m
.group('path').replace('\\"', '"')
128 if p
== '.' or m
.group().find('= -1 ENOENT') > -1:
129 # just to speed it up a bit
132 if not os
.path
.isabs(p
):
133 p
= os
.path
.join(pid_to_cwd
.get(pid
, cwd
), p
)
135 call
= m
.group('call')
148 if p
.endswith('/') or os
.path
.isdir(p
):
154 strace_lock
.acquire()
156 cache
[p
] = node
= bld
.root
.find_node(p
)
160 strace_lock
.release()
163 # record the dependencies then force the task signature recalculation for next time
165 Logs
.debug('deps: real scanner for %r returned %r', self
, nodes
)
166 bld
= self
.generator
.bld
167 bld
.node_deps
[self
.uid()] = nodes
168 bld
.raw_deps
[self
.uid()] = []
171 except AttributeError: