waf/waflib/extras/gccdeps.py

245 lines
7.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
2015-03-02 16:54:56 +01:00
and prepare the dependency calculation for the next run.
This affects the cxx class, so make sure to load Qt5 after this tool.
2015-03-02 16:54:56 +01:00
Usage::
def options(opt):
opt.load('compiler_cxx')
2015-03-02 16:54:56 +01:00
def configure(conf):
conf.load('compiler_cxx gccdeps')
2011-09-10 11:13:51 +02:00
"""
import os, re, threading
2011-09-10 11:13:51 +02:00
from waflib import Task, Logs, Utils, Errors
2022-09-01 01:02:59 +02:00
from waflib.Tools import asm, c, c_preproc, cxx
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()
2015-03-02 16:54:56 +01:00
gccdeps_flags = ['-MD']
if not c_preproc.go_absolute:
gccdeps_flags = ['-MMD']
2011-09-10 11:13:51 +02:00
# Third-party tools are allowed to add extra names in here with append()
supported_compilers = ['gas', 'gcc', 'icc', 'clang']
re_o = re.compile(r"\.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:
node = cached_nodes[node_lookup_key]
except KeyError:
# retry with lock on cache miss
with lock:
try:
node = cached_nodes[node_lookup_key]
except KeyError:
node = cached_nodes[node_lookup_key] = base_node.find_resource(path)
return node
2011-09-10 11:13:51 +02:00
def post_run(self):
2015-03-02 16:54:56 +01:00
if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
return super(self.derived_gccdeps, self).post_run()
2012-05-04 09:25:48 +02:00
deps_filename = self.outputs[0].abspath()
deps_filename = re_o.sub('.d', deps_filename)
try:
deps_txt = Utils.readf(deps_filename)
except EnvironmentError:
Logs.error('Could not find a .d dependency file, are cflags/cxxflags overwritten?')
raise
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]
deps_txt = '\n'.join([remove_makefile_rule_lhs(line) for line in deps_txt.splitlines()])
# Now join all the lines together
deps_txt = deps_txt.replace('\\\n', '')
2011-09-10 11:13:51 +02:00
dep_paths = deps_txt.strip()
dep_paths = [x.replace('\\ ', ' ') for x in re_splitter.split(dep_paths) if x]
2011-09-10 11:13:51 +02:00
resolved_nodes = []
unresolved_names = []
2011-09-10 11:13:51 +02:00
bld = self.generator.bld
# Dynamically bind to the cache
try:
cached_nodes = bld.cached_nodes
except AttributeError:
cached_nodes = bld.cached_nodes = {}
for path in dep_paths:
2011-09-10 11:13:51 +02:00
node = None
if os.path.isabs(path):
node = path_to_node(bld.root, path, cached_nodes)
2011-09-10 11:13:51 +02:00
else:
# TODO waf 1.9 - single cwd value
base_node = getattr(bld, 'cwdx', bld.bldnode)
# when calling find_resource, make sure the path does not contain '..'
path = [k for k in Utils.split_path(path) if k and k != '.']
while '..' in path:
idx = path.index('..')
if idx == 0:
path = path[1:]
base_node = base_node.parent
else:
del path[idx]
del path[idx-1]
node = path_to_node(base_node, path, cached_nodes)
2011-09-10 11:13:51 +02:00
if not node:
raise ValueError('could not find %r for %r' % (path, self))
2015-03-02 16:54:56 +01: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
2011-09-10 11:13:51 +02:00
resolved_nodes.append(node)
Logs.debug('deps: gccdeps for %s returned %s', self, resolved_nodes)
2011-09-10 11:13:51 +02:00
bld.node_deps[self.uid()] = resolved_nodes
bld.raw_deps[self.uid()] = unresolved_names
2011-09-10 11:13:51 +02:00
try:
del self.cache_sig
except AttributeError:
2011-09-10 11:13:51 +02:00
pass
Task.Task.post_run(self)
def scan(self):
if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
return super(self.derived_gccdeps, self).scan()
resolved_nodes = self.generator.bld.node_deps.get(self.uid(), [])
unresolved_names = []
return (resolved_nodes, unresolved_names)
2011-09-10 11:13:51 +02:00
def sig_implicit_deps(self):
2015-03-02 16:54:56 +01:00
if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
return super(self.derived_gccdeps, self).sig_implicit_deps()
bld = self.generator.bld
2011-09-10 11:13:51 +02:00
try:
return self.compute_sig_implicit_deps()
except Errors.TaskNotReady:
raise ValueError("Please specify the build order precisely with gccdeps (asm/c/c++ tasks)")
except EnvironmentError:
# If a file is renamed, assume the dependencies are stale and must be recalculated
for x in bld.node_deps.get(self.uid(), []):
if not x.is_bld() and not x.exists():
try:
del x.parent.children[x.name]
except KeyError:
pass
key = self.uid()
bld.node_deps[key] = []
bld.raw_deps[key] = []
return Utils.SIG_NIL
2011-09-10 11:13:51 +02:00
def wrap_compiled_task(classname):
derived_class = type(classname, (Task.classes[classname],), {})
derived_class.derived_gccdeps = derived_class
derived_class.post_run = post_run
derived_class.scan = scan
derived_class.sig_implicit_deps = sig_implicit_deps
2012-05-04 09:25:48 +02:00
for k in ('asm', 'c', 'cxx'):
if k in Task.classes:
wrap_compiled_task(k)
2011-09-10 11:13:51 +02:00
2015-03-02 16:54:56 +01:00
@before_method('process_source')
@feature('force_gccdeps')
def force_gccdeps(self):
self.env.ENABLE_GCCDEPS = ['asm', 'c', 'cxx']
2015-03-02 16:54:56 +01:00
def configure(conf):
# in case someone provides a --enable-gccdeps command-line option
if not getattr(conf.options, 'enable_gccdeps', True):
return
2015-03-02 16:54:56 +01:00
global gccdeps_flags
flags = conf.env.GCCDEPS_FLAGS or gccdeps_flags
if conf.env.ASM_NAME in supported_compilers:
try:
conf.check(fragment='', features='asm force_gccdeps', asflags=flags, compile_filename='test.S', msg='Checking for asm flags %r' % ''.join(flags))
except Errors.ConfigurationError:
pass
else:
conf.env.append_value('ASFLAGS', flags)
conf.env.append_unique('ENABLE_GCCDEPS', 'asm')
2015-03-02 16:54:56 +01:00
if conf.env.CC_NAME in supported_compilers:
try:
conf.check(fragment='int main() { return 0; }', features='c force_gccdeps', cflags=flags, msg='Checking for c flags %r' % ''.join(flags))
except Errors.ConfigurationError:
pass
else:
conf.env.append_value('CFLAGS', flags)
2015-03-02 16:54:56 +01:00
conf.env.append_unique('ENABLE_GCCDEPS', 'c')
if conf.env.CXX_NAME in supported_compilers:
try:
conf.check(fragment='int main() { return 0; }', features='cxx force_gccdeps', cxxflags=flags, msg='Checking for cxx flags %r' % ''.join(flags))
except Errors.ConfigurationError:
pass
else:
conf.env.append_value('CXXFLAGS', flags)
2015-03-02 16:54:56 +01:00
conf.env.append_unique('ENABLE_GCCDEPS', 'cxx')
def options(opt):
raise ValueError('Do not load gccdeps options')