jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
4.0KB

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