waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

stracedeps.py (4102B)


      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 	if not 'cwd' in kw:
     70 		kw['cwd'] = self.get_cwd()
     71 
     72 	args = self.get_strace_args()
     73 	fname = self.get_strace_file()
     74 	if isinstance(cmd, list):
     75 		cmd = args + cmd
     76 	else:
     77 		cmd = '%s %s' % (' '.join(args), cmd)
     78 
     79 	try:
     80 		ret = bld.exec_command(cmd, **kw)
     81 	finally:
     82 		if not ret:
     83 			self.parse_strace_deps(fname, kw['cwd'])
     84 	return ret
     85 
     86 @task_method
     87 def sig_implicit_deps(self):
     88 	# bypass the scanner functions
     89 	return
     90 
     91 @task_method
     92 def parse_strace_deps(self, path, cwd):
     93 	# uncomment the following line to disable the dependencies and force a file scan
     94 	# return
     95 	try:
     96 		cnt = Utils.readf(path)
     97 	finally:
     98 		try:
     99 			os.remove(path)
    100 		except OSError:
    101 			pass
    102 
    103 	if not isinstance(cwd, str):
    104 		cwd = cwd.abspath()
    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 %r returned %r', self, 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()
    174