waf/waflib/extras/gccdeps.py

192 lines
5.2 KiB
Python
Raw Normal View History

2011-09-10 11:13:51 +02:00
#!/usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2008-2010 (ita)
"""
Execute the tasks with gcc -MD, read the dependencies from the .d file
and prepare the dependency calculation for the next run
"""
import os, re, threading
from waflib import Task, Logs, Utils, Errors
from waflib.Tools import c_preproc
2012-07-13 19:56:38 +02:00
from waflib.TaskGen import before_method, feature
2011-09-10 11:13:51 +02:00
lock = threading.Lock()
preprocessor_flag = '-MD'
# Third-party tools are allowed to add extra names in here with append()
2015-02-20 14:43:55 +01:00
supported_compilers = ['gcc', 'icc', 'clang']
2012-05-04 09:25:48 +02:00
@feature('c')
@before_method('process_source')
2011-09-10 11:13:51 +02:00
def add_mmd_cc(self):
if self.env.CC_NAME in supported_compilers and self.env.get_flat('CFLAGS').find(preprocessor_flag) < 0:
2011-09-10 11:13:51 +02:00
self.env.append_value('CFLAGS', [preprocessor_flag])
2015-02-20 14:43:55 +01:00
self.env.HAS_GCCDEPS = 1
2011-09-10 11:13:51 +02:00
@feature('cxx')
2012-05-04 09:25:48 +02:00
@before_method('process_source')
2011-09-10 11:13:51 +02:00
def add_mmd_cxx(self):
if self.env.CXX_NAME in supported_compilers and self.env.get_flat('CXXFLAGS').find(preprocessor_flag) < 0:
2011-09-10 11:13:51 +02:00
self.env.append_value('CXXFLAGS', [preprocessor_flag])
2015-02-20 14:43:55 +01:00
self.env.HAS_GCCDEPS = 1
2011-09-10 11:13:51 +02:00
def scan(self):
2015-02-20 14:43:55 +01:00
if not self.env.HAS_GCCDEPS:
2012-05-04 09:25:48 +02:00
return self.no_gccdeps_scan()
2011-09-10 11:13:51 +02:00
nodes = self.generator.bld.node_deps.get(self.uid(), [])
names = []
return (nodes, names)
re_o = re.compile("\.o$")
2012-05-04 14:32:25 +02:00
re_splitter = re.compile(r'(?<!\\)\s+') # split by space, except when spaces are escaped
2012-07-13 19:56:38 +02:00
def remove_makefile_rule_lhs(line):
# Splitting on a plain colon would accidentally match inside a
# Windows absolute-path filename, so we must search for a colon
# followed by whitespace to find the divider between LHS and RHS
# of the Makefile rule.
rulesep = ': '
sep_idx = line.find(rulesep)
if sep_idx >= 0:
return line[sep_idx + 2:]
else:
return line
def path_to_node(base_node, path, cached_nodes):
# Take the base node and the path and return a node
# Results are cached because searching the node tree is expensive
# The following code is executed by threads, it is not safe, so a lock is needed...
if getattr(path, '__hash__'):
node_lookup_key = (base_node, path)
else:
# Not hashable, assume it is a list and join into a string
node_lookup_key = (base_node, os.path.sep.join(path))
try:
lock.acquire()
node = cached_nodes[node_lookup_key]
except KeyError:
node = base_node.find_resource(path)
cached_nodes[node_lookup_key] = node
finally:
lock.release()
return node
2011-09-10 11:13:51 +02:00
def post_run(self):
# The following code is executed by threads, it is not safe, so a lock is needed...
if self.env.CC_NAME not in supported_compilers:
2012-05-04 09:25:48 +02:00
return self.no_gccdeps_post_run()
2011-09-10 11:13:51 +02:00
if getattr(self, 'cached', None):
return Task.Task.post_run(self)
name = self.outputs[0].abspath()
name = re_o.sub('.d', name)
txt = Utils.readf(name)
#os.remove(name)
2011-09-10 11:13:51 +02:00
# Compilers have the choice to either output the file's dependencies
# as one large Makefile rule:
#
# /path/to/file.o: /path/to/dep1.h \
# /path/to/dep2.h \
# /path/to/dep3.h \
# ...
#
# or as many individual rules:
#
# /path/to/file.o: /path/to/dep1.h
# /path/to/file.o: /path/to/dep2.h
# /path/to/file.o: /path/to/dep3.h
# ...
#
# So the first step is to sanitize the input by stripping out the left-
# hand side of all these lines. After that, whatever remains are the
# implicit dependencies of task.outputs[0]
txt = '\n'.join([remove_makefile_rule_lhs(line) for line in txt.splitlines()])
# Now join all the lines together
2011-09-10 11:13:51 +02:00
txt = txt.replace('\\\n', '')
val = txt.strip()
lst = val.split(':')
2012-05-04 19:43:13 +02:00
val = [x.replace('\\ ', ' ') for x in re_splitter.split(val) if x]
2011-09-10 11:13:51 +02:00
nodes = []
bld = self.generator.bld
# Dynamically bind to the cache
try:
cached_nodes = bld.cached_nodes
except AttributeError:
cached_nodes = bld.cached_nodes = {}
2011-09-10 11:13:51 +02:00
for x in val:
node = None
if os.path.isabs(x):
node = path_to_node(bld.root, x, cached_nodes)
2011-09-10 11:13:51 +02:00
else:
path = bld.bldnode
# when calling find_resource, make sure the path does not begin by '..'
2011-09-10 11:13:51 +02:00
x = [k for k in Utils.split_path(x) if k and k != '.']
while lst and x[0] == '..':
x = x[1:]
path = path.parent
node = path_to_node(path, x, cached_nodes)
2011-09-10 11:13:51 +02:00
if not node:
raise ValueError('could not find %r for %r' % (x, self))
else:
2012-05-04 23:13:13 +02:00
if not c_preproc.go_absolute:
if not (node.is_child_of(bld.srcnode) or node.is_child_of(bld.bldnode)):
continue
2011-09-10 11:13:51 +02:00
if id(node) == id(self.inputs[0]):
# ignore the source file, it is already in the dependencies
# this way, successful config tests may be retrieved from the cache
continue
nodes.append(node)
Logs.debug('deps: real scanner for %s returned %s' % (str(self), str(nodes)))
bld.node_deps[self.uid()] = nodes
bld.raw_deps[self.uid()] = []
try:
del self.cache_sig
except:
pass
Task.Task.post_run(self)
def sig_implicit_deps(self):
if self.env.CC_NAME not in supported_compilers:
2012-05-04 09:25:48 +02:00
return self.no_gccdeps_sig_implicit_deps()
2011-09-10 11:13:51 +02:00
try:
return Task.Task.sig_implicit_deps(self)
except Errors.WafError:
return Utils.SIG_NIL
2012-05-04 09:25:48 +02:00
for name in 'c cxx'.split():
2011-09-10 11:13:51 +02:00
try:
cls = Task.classes[name]
except KeyError:
pass
else:
2012-05-04 09:25:48 +02:00
cls.no_gccdeps_post_run = cls.post_run
cls.no_gccdeps_scan = cls.scan
cls.no_gccdeps_sig_implicit_deps = cls.sig_implicit_deps
2011-09-10 11:13:51 +02:00
cls.post_run = post_run
cls.scan = scan
cls.sig_implicit_deps = sig_implicit_deps