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