1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
#!/usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2015 (ita)
"""
Execute tasks through strace to obtain dependencies after the process is run. This
scheme is similar to that of the Fabricate script.
To use::
def configure(conf):
conf.load('strace')
WARNING:
* This will not work when advanced scanners are needed (qt4/qt5)
* The overhead of running 'strace' is significant (56s -> 1m29s)
* It will not work on Windows :-)
"""
import os, re, threading
from waflib import Task, Logs, Utils
#TRACECALLS = 'trace=access,chdir,clone,creat,execve,exit_group,fork,lstat,lstat64,mkdir,open,rename,stat,stat64,symlink,vfork'
TRACECALLS = 'trace=process,file'
BANNED = ('/tmp', '/proc', '/sys', '/dev')
s_process = r'(?:clone|fork|vfork)\(.*?(?P<npid>\d+)'
s_file = r'(?P<call>\w+)\("(?P<path>([^"\\]|\\.)*)"(.*)'
re_lines = re.compile(r'^(?P<pid>\d+)\s+(?:(?:%s)|(?:%s))\r*$' % (s_file, s_process), re.IGNORECASE | re.MULTILINE)
strace_lock = threading.Lock()
def configure(conf):
conf.find_program('strace')
def task_method(func):
# Decorator function to bind/replace methods on the base Task class
#
# The methods Task.exec_command and Task.sig_implicit_deps already exists and are rarely overridden
# we thus expect that we are the only ones doing this
try:
setattr(Task.Task, 'nostrace_%s' % func.__name__, getattr(Task.Task, func.__name__))
except AttributeError:
pass
setattr(Task.Task, func.__name__, func)
return func
@task_method
def get_strace_file(self):
try:
return self.strace_file
except AttributeError:
pass
if self.outputs:
ret = self.outputs[0].abspath() + '.strace'
else:
ret = '%s%s%d%s' % (self.generator.bld.bldnode.abspath(), os.sep, id(self), '.strace')
self.strace_file = ret
return ret
@task_method
def get_strace_args(self):
return (self.env.STRACE or ['strace']) + ['-e', TRACECALLS, '-f', '-o', self.get_strace_file()]
@task_method
def exec_command(self, cmd, **kw):
bld = self.generator.bld
if not 'cwd' in kw:
kw['cwd'] = self.get_cwd()
args = self.get_strace_args()
fname = self.get_strace_file()
if isinstance(cmd, list):
cmd = args + cmd
else:
cmd = '%s %s' % (' '.join(args), cmd)
try:
ret = bld.exec_command(cmd, **kw)
finally:
if not ret:
self.parse_strace_deps(fname, kw['cwd'])
return ret
@task_method
def sig_implicit_deps(self):
# bypass the scanner functions
return
@task_method
def parse_strace_deps(self, path, cwd):
# uncomment the following line to disable the dependencies and force a file scan
# return
try:
cnt = Utils.readf(path)
finally:
try:
os.remove(path)
except OSError:
pass
if not isinstance(cwd, str):
cwd = cwd.abspath()
nodes = []
bld = self.generator.bld
try:
cache = bld.strace_cache
except AttributeError:
cache = bld.strace_cache = {}
# chdir and relative paths
pid_to_cwd = {}
global BANNED
done = set()
for m in re.finditer(re_lines, cnt):
# scraping the output of strace
pid = m.group('pid')
if m.group('npid'):
npid = m.group('npid')
pid_to_cwd[npid] = pid_to_cwd.get(pid, cwd)
continue
p = m.group('path').replace('\\"', '"')
if p == '.' or m.group().find('= -1 ENOENT') > -1:
# just to speed it up a bit
continue
if not os.path.isabs(p):
p = os.path.join(pid_to_cwd.get(pid, cwd), p)
call = m.group('call')
if call == 'chdir':
pid_to_cwd[pid] = p
continue
if p in done:
continue
done.add(p)
for x in BANNED:
if p.startswith(x):
break
else:
if p.endswith('/') or os.path.isdir(p):
continue
try:
node = cache[p]
except KeyError:
strace_lock.acquire()
try:
cache[p] = node = bld.root.find_node(p)
if not node:
continue
finally:
strace_lock.release()
nodes.append(node)
# record the dependencies then force the task signature recalculation for next time
if Logs.verbose:
Logs.debug('deps: real scanner for %r returned %r', self, nodes)
bld = self.generator.bld
bld.node_deps[self.uid()] = nodes
bld.raw_deps[self.uid()] = []
try:
del self.cache_sig
except AttributeError:
pass
self.signature()
|