waf

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

gccdeps.py (7375B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2008-2010 (ita)
      4 
      5 """
      6 Execute the tasks with gcc -MD, read the dependencies from the .d file
      7 and prepare the dependency calculation for the next run.
      8 This affects the cxx class, so make sure to load Qt5 after this tool.
      9 
     10 Usage::
     11 
     12 	def options(opt):
     13 		opt.load('compiler_cxx')
     14 	def configure(conf):
     15 		conf.load('compiler_cxx gccdeps')
     16 """
     17 
     18 import os, re, threading
     19 from waflib import Task, Logs, Utils, Errors
     20 from waflib.Tools import asm, c, c_preproc, cxx
     21 from waflib.TaskGen import before_method, feature
     22 
     23 lock = threading.Lock()
     24 
     25 gccdeps_flags = ['-MD']
     26 if not c_preproc.go_absolute:
     27 	gccdeps_flags = ['-MMD']
     28 
     29 # Third-party tools are allowed to add extra names in here with append()
     30 supported_compilers = ['gas', 'gcc', 'icc', 'clang']
     31 
     32 re_o = re.compile(r"\.o$")
     33 re_splitter = re.compile(r'(?<!\\)\s+') # split by space, except when spaces are escaped
     34 
     35 def remove_makefile_rule_lhs(line):
     36 	# Splitting on a plain colon would accidentally match inside a
     37 	# Windows absolute-path filename, so we must search for a colon
     38 	# followed by whitespace to find the divider between LHS and RHS
     39 	# of the Makefile rule.
     40 	rulesep = ': '
     41 
     42 	sep_idx = line.find(rulesep)
     43 	if sep_idx >= 0:
     44 		return line[sep_idx + 2:]
     45 	else:
     46 		return line
     47 
     48 def path_to_node(base_node, path, cached_nodes):
     49 	# Take the base node and the path and return a node
     50 	# Results are cached because searching the node tree is expensive
     51 	# The following code is executed by threads, it is not safe, so a lock is needed...
     52 	if getattr(path, '__hash__'):
     53 		node_lookup_key = (base_node, path)
     54 	else:
     55 		# Not hashable, assume it is a list and join into a string
     56 		node_lookup_key = (base_node, os.path.sep.join(path))
     57 
     58 	try:
     59 		node = cached_nodes[node_lookup_key]
     60 	except KeyError:
     61 		# retry with lock on cache miss
     62 		with lock:
     63 			try:
     64 				node = cached_nodes[node_lookup_key]
     65 			except KeyError:
     66 				node = cached_nodes[node_lookup_key] = base_node.find_resource(path)
     67 
     68 	return node
     69 
     70 def post_run(self):
     71 	if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
     72 		return super(self.derived_gccdeps, self).post_run()
     73 
     74 	deps_filename = self.outputs[0].abspath()
     75 	deps_filename = re_o.sub('.d', deps_filename)
     76 	try:
     77 		deps_txt = Utils.readf(deps_filename)
     78 	except EnvironmentError:
     79 		Logs.error('Could not find a .d dependency file, are cflags/cxxflags overwritten?')
     80 		raise
     81 
     82 	# Compilers have the choice to either output the file's dependencies
     83 	# as one large Makefile rule:
     84 	#
     85 	#   /path/to/file.o: /path/to/dep1.h \
     86 	#                    /path/to/dep2.h \
     87 	#                    /path/to/dep3.h \
     88 	#                    ...
     89 	#
     90 	# or as many individual rules:
     91 	#
     92 	#   /path/to/file.o: /path/to/dep1.h
     93 	#   /path/to/file.o: /path/to/dep2.h
     94 	#   /path/to/file.o: /path/to/dep3.h
     95 	#   ...
     96 	#
     97 	# So the first step is to sanitize the input by stripping out the left-
     98 	# hand side of all these lines. After that, whatever remains are the
     99 	# implicit dependencies of task.outputs[0]
    100 	deps_txt = '\n'.join([remove_makefile_rule_lhs(line) for line in deps_txt.splitlines()])
    101 
    102 	# Now join all the lines together
    103 	deps_txt = deps_txt.replace('\\\n', '')
    104 
    105 	dep_paths = deps_txt.strip()
    106 	dep_paths = [x.replace('\\ ', ' ') for x in re_splitter.split(dep_paths) if x]
    107 
    108 	resolved_nodes = []
    109 	unresolved_names = []
    110 	bld = self.generator.bld
    111 
    112 	# Dynamically bind to the cache
    113 	try:
    114 		cached_nodes = bld.cached_nodes
    115 	except AttributeError:
    116 		cached_nodes = bld.cached_nodes = {}
    117 
    118 	for path in dep_paths:
    119 
    120 		node = None
    121 		if os.path.isabs(path):
    122 			node = path_to_node(bld.root, path, cached_nodes)
    123 		else:
    124 			# TODO waf 1.9 - single cwd value
    125 			base_node = getattr(bld, 'cwdx', bld.bldnode)
    126 			# when calling find_resource, make sure the path does not contain '..'
    127 			path = [k for k in Utils.split_path(path) if k and k != '.']
    128 			while '..' in path:
    129 				idx = path.index('..')
    130 				if idx == 0:
    131 					path = path[1:]
    132 					base_node = base_node.parent
    133 				else:
    134 					del path[idx]
    135 					del path[idx-1]
    136 
    137 			node = path_to_node(base_node, path, cached_nodes)
    138 
    139 		if not node:
    140 			raise ValueError('could not find %r for %r' % (path, self))
    141 
    142 		if id(node) == id(self.inputs[0]):
    143 			# ignore the source file, it is already in the dependencies
    144 			# this way, successful config tests may be retrieved from the cache
    145 			continue
    146 
    147 		resolved_nodes.append(node)
    148 
    149 	Logs.debug('deps: gccdeps for %s returned %s', self, resolved_nodes)
    150 
    151 	bld.node_deps[self.uid()] = resolved_nodes
    152 	bld.raw_deps[self.uid()] = unresolved_names
    153 
    154 	try:
    155 		del self.cache_sig
    156 	except AttributeError:
    157 		pass
    158 
    159 	Task.Task.post_run(self)
    160 
    161 def scan(self):
    162 	if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
    163 		return super(self.derived_gccdeps, self).scan()
    164 
    165 	resolved_nodes = self.generator.bld.node_deps.get(self.uid(), [])
    166 	unresolved_names = []
    167 	return (resolved_nodes, unresolved_names)
    168 
    169 def sig_implicit_deps(self):
    170 	if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
    171 		return super(self.derived_gccdeps, self).sig_implicit_deps()
    172 	bld = self.generator.bld
    173 
    174 	try:
    175 		return self.compute_sig_implicit_deps()
    176 	# except Errors.TaskNotReady:
    177 	# 	raise ValueError("Please specify the build order precisely with gccdeps (asm/c/c++ tasks)")
    178 	except EnvironmentError:
    179 		# If a file is renamed, assume the dependencies are stale and must be recalculated
    180 		for x in bld.node_deps.get(self.uid(), []):
    181 			if not x.is_bld() and not x.exists():
    182 				try:
    183 					del x.parent.children[x.name]
    184 				except KeyError:
    185 					pass
    186 
    187 	key = self.uid()
    188 	bld.node_deps[key] = []
    189 	bld.raw_deps[key] = []
    190 	return Utils.SIG_NIL
    191 
    192 def wrap_compiled_task(classname):
    193 	derived_class = type(classname, (Task.classes[classname],), {})
    194 	derived_class.derived_gccdeps = derived_class
    195 	derived_class.post_run = post_run
    196 	derived_class.scan = scan
    197 	derived_class.sig_implicit_deps = sig_implicit_deps
    198 
    199 for k in ('asm', 'c', 'cxx'):
    200 	if k in Task.classes:
    201 		wrap_compiled_task(k)
    202 
    203 @before_method('process_source')
    204 @feature('force_gccdeps')
    205 def force_gccdeps(self):
    206 	self.env.ENABLE_GCCDEPS = ['asm', 'c', 'cxx']
    207 
    208 def configure(conf):
    209 	# in case someone provides a --enable-gccdeps command-line option
    210 	if not getattr(conf.options, 'enable_gccdeps', True):
    211 		return
    212 
    213 	global gccdeps_flags
    214 	flags = conf.env.GCCDEPS_FLAGS or gccdeps_flags
    215 	if conf.env.ASM_NAME in supported_compilers:
    216 		try:
    217 			conf.check(fragment='', features='asm force_gccdeps', asflags=flags, compile_filename='test.S', msg='Checking for asm flags %r' % ''.join(flags))
    218 		except Errors.ConfigurationError:
    219 			pass
    220 		else:
    221 			conf.env.append_value('ASFLAGS', flags)
    222 			conf.env.append_unique('ENABLE_GCCDEPS', 'asm')
    223 
    224 	if conf.env.CC_NAME in supported_compilers:
    225 		try:
    226 			conf.check(fragment='int main() { return 0; }', features='c force_gccdeps', cflags=flags, msg='Checking for c flags %r' % ''.join(flags))
    227 		except Errors.ConfigurationError:
    228 			pass
    229 		else:
    230 			conf.env.append_value('CFLAGS', flags)
    231 			conf.env.append_unique('ENABLE_GCCDEPS', 'c')
    232 
    233 	if conf.env.CXX_NAME in supported_compilers:
    234 		try:
    235 			conf.check(fragment='int main() { return 0; }', features='cxx force_gccdeps', cxxflags=flags, msg='Checking for cxx flags %r' % ''.join(flags))
    236 		except Errors.ConfigurationError:
    237 			pass
    238 		else:
    239 			conf.env.append_value('CXXFLAGS', flags)
    240 			conf.env.append_unique('ENABLE_GCCDEPS', 'cxx')
    241 
    242 def options(opt):
    243 	raise ValueError('Do not load gccdeps options')
    244