Back to home page

LXR

 
 

    


File indexing completed on 2025-05-11 08:24:54

0001 #!/usr/bin/env python3
0002 # encoding: utf-8
0003 # Thomas Nagy, 2008-2010 (ita)
0004 
0005 """
0006 Execute the tasks with gcc -MD, read the dependencies from the .d file
0007 and prepare the dependency calculation for the next run.
0008 This affects the cxx class, so make sure to load Qt5 after this tool.
0009 
0010 Usage::
0011 
0012     def options(opt):
0013         opt.load('compiler_cxx')
0014     def configure(conf):
0015         conf.load('compiler_cxx gccdeps')
0016 """
0017 
0018 import os, re, threading
0019 from waflib import Task, Logs, Utils, Errors
0020 from waflib.Tools import asm, c, c_preproc, cxx
0021 from waflib.TaskGen import before_method, feature
0022 
0023 lock = threading.Lock()
0024 
0025 gccdeps_flags = ['-MD']
0026 if not c_preproc.go_absolute:
0027     gccdeps_flags = ['-MMD']
0028 
0029 # Third-party tools are allowed to add extra names in here with append()
0030 supported_compilers = ['gas', 'gcc', 'icc', 'clang']
0031 
0032 re_o = re.compile(r"\.o$")
0033 re_splitter = re.compile(r'(?<!\\)\s+') # split by space, except when spaces are escaped
0034 
0035 def remove_makefile_rule_lhs(line):
0036     # Splitting on a plain colon would accidentally match inside a
0037     # Windows absolute-path filename, so we must search for a colon
0038     # followed by whitespace to find the divider between LHS and RHS
0039     # of the Makefile rule.
0040     rulesep = ': '
0041 
0042     sep_idx = line.find(rulesep)
0043     if sep_idx >= 0:
0044         return line[sep_idx + 2:]
0045     else:
0046         return line
0047 
0048 def path_to_node(base_node, path, cached_nodes):
0049     # Take the base node and the path and return a node
0050     # Results are cached because searching the node tree is expensive
0051     # The following code is executed by threads, it is not safe, so a lock is needed...
0052     if getattr(path, '__hash__'):
0053         node_lookup_key = (base_node, path)
0054     else:
0055         # Not hashable, assume it is a list and join into a string
0056         node_lookup_key = (base_node, os.path.sep.join(path))
0057 
0058     try:
0059         node = cached_nodes[node_lookup_key]
0060     except KeyError:
0061         # retry with lock on cache miss
0062         with lock:
0063             try:
0064                 node = cached_nodes[node_lookup_key]
0065             except KeyError:
0066                 node = cached_nodes[node_lookup_key] = base_node.find_resource(path)
0067 
0068     return node
0069 
0070 def post_run(self):
0071     if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
0072         return super(self.derived_gccdeps, self).post_run()
0073 
0074     deps_filename = self.outputs[0].abspath()
0075     deps_filename = re_o.sub('.d', deps_filename)
0076     try:
0077         deps_txt = Utils.readf(deps_filename)
0078     except EnvironmentError:
0079         Logs.error('Could not find a .d dependency file, are cflags/cxxflags overwritten?')
0080         raise
0081 
0082     # Compilers have the choice to either output the file's dependencies
0083     # as one large Makefile rule:
0084     #
0085     #   /path/to/file.o: /path/to/dep1.h \
0086     #                    /path/to/dep2.h \
0087     #                    /path/to/dep3.h \
0088     #                    ...
0089     #
0090     # or as many individual rules:
0091     #
0092     #   /path/to/file.o: /path/to/dep1.h
0093     #   /path/to/file.o: /path/to/dep2.h
0094     #   /path/to/file.o: /path/to/dep3.h
0095     #   ...
0096     #
0097     # So the first step is to sanitize the input by stripping out the left-
0098     # hand side of all these lines. After that, whatever remains are the
0099     # implicit dependencies of task.outputs[0]
0100     deps_txt = '\n'.join([remove_makefile_rule_lhs(line) for line in deps_txt.splitlines()])
0101 
0102     # Now join all the lines together
0103     deps_txt = deps_txt.replace('\\\n', '')
0104 
0105     dep_paths = deps_txt.strip()
0106     dep_paths = [x.replace('\\ ', ' ') for x in re_splitter.split(dep_paths) if x]
0107 
0108     resolved_nodes = []
0109     unresolved_names = []
0110     bld = self.generator.bld
0111 
0112     # Dynamically bind to the cache
0113     try:
0114         cached_nodes = bld.cached_nodes
0115     except AttributeError:
0116         cached_nodes = bld.cached_nodes = {}
0117 
0118     for path in dep_paths:
0119 
0120         node = None
0121         if os.path.isabs(path):
0122             node = path_to_node(bld.root, path, cached_nodes)
0123         else:
0124             # TODO waf 1.9 - single cwd value
0125             base_node = getattr(bld, 'cwdx', bld.bldnode)
0126             # when calling find_resource, make sure the path does not contain '..'
0127             path = [k for k in Utils.split_path(path) if k and k != '.']
0128             while '..' in path:
0129                 idx = path.index('..')
0130                 if idx == 0:
0131                     path = path[1:]
0132                     base_node = base_node.parent
0133                 else:
0134                     del path[idx]
0135                     del path[idx-1]
0136 
0137             node = path_to_node(base_node, path, cached_nodes)
0138 
0139         if not node:
0140             raise ValueError('could not find %r for %r' % (path, self))
0141 
0142         if id(node) == id(self.inputs[0]):
0143             # ignore the source file, it is already in the dependencies
0144             # this way, successful config tests may be retrieved from the cache
0145             continue
0146 
0147         resolved_nodes.append(node)
0148 
0149     Logs.debug('deps: gccdeps for %s returned %s', self, resolved_nodes)
0150 
0151     bld.node_deps[self.uid()] = resolved_nodes
0152     bld.raw_deps[self.uid()] = unresolved_names
0153 
0154     try:
0155         del self.cache_sig
0156     except AttributeError:
0157         pass
0158 
0159     Task.Task.post_run(self)
0160 
0161 def scan(self):
0162     if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
0163         return super(self.derived_gccdeps, self).scan()
0164 
0165     resolved_nodes = self.generator.bld.node_deps.get(self.uid(), [])
0166     unresolved_names = []
0167     return (resolved_nodes, unresolved_names)
0168 
0169 def sig_implicit_deps(self):
0170     if not self.__class__.__name__ in self.env.ENABLE_GCCDEPS:
0171         return super(self.derived_gccdeps, self).sig_implicit_deps()
0172     bld = self.generator.bld
0173 
0174     try:
0175         return self.compute_sig_implicit_deps()
0176     except Errors.TaskNotReady:
0177         raise ValueError("Please specify the build order precisely with gccdeps (asm/c/c++ tasks)")
0178     except EnvironmentError:
0179         # If a file is renamed, assume the dependencies are stale and must be recalculated
0180         for x in bld.node_deps.get(self.uid(), []):
0181             if not x.is_bld() and not x.exists():
0182                 try:
0183                     del x.parent.children[x.name]
0184                 except KeyError:
0185                     pass
0186 
0187     key = self.uid()
0188     bld.node_deps[key] = []
0189     bld.raw_deps[key] = []
0190     return Utils.SIG_NIL
0191 
0192 def wrap_compiled_task(classname):
0193     derived_class = type(classname, (Task.classes[classname],), {})
0194     derived_class.derived_gccdeps = derived_class
0195     derived_class.post_run = post_run
0196     derived_class.scan = scan
0197     derived_class.sig_implicit_deps = sig_implicit_deps
0198 
0199 for k in ('asm', 'c', 'cxx'):
0200     if k in Task.classes:
0201         wrap_compiled_task(k)
0202 
0203 @before_method('process_source')
0204 @feature('force_gccdeps')
0205 def force_gccdeps(self):
0206     self.env.ENABLE_GCCDEPS = ['asm', 'c', 'cxx']
0207 
0208 def configure(conf):
0209     # in case someone provides a --enable-gccdeps command-line option
0210     if not getattr(conf.options, 'enable_gccdeps', True):
0211         return
0212 
0213     global gccdeps_flags
0214     flags = conf.env.GCCDEPS_FLAGS or gccdeps_flags
0215     if conf.env.ASM_NAME in supported_compilers:
0216         try:
0217             conf.check(fragment='', features='asm force_gccdeps', asflags=flags, compile_filename='test.S', msg='Checking for asm flags %r' % ''.join(flags))
0218         except Errors.ConfigurationError:
0219             pass
0220         else:
0221             conf.env.append_value('ASFLAGS', flags)
0222             conf.env.append_unique('ENABLE_GCCDEPS', 'asm')
0223 
0224     if conf.env.CC_NAME in supported_compilers:
0225         try:
0226             conf.check(fragment='int main() { return 0; }', features='c force_gccdeps', cflags=flags, msg='Checking for c flags %r' % ''.join(flags))
0227         except Errors.ConfigurationError:
0228             pass
0229         else:
0230             conf.env.append_value('CFLAGS', flags)
0231             conf.env.append_unique('ENABLE_GCCDEPS', 'c')
0232 
0233     if conf.env.CXX_NAME in supported_compilers:
0234         try:
0235             conf.check(fragment='int main() { return 0; }', features='cxx force_gccdeps', cxxflags=flags, msg='Checking for cxx flags %r' % ''.join(flags))
0236         except Errors.ConfigurationError:
0237             pass
0238         else:
0239             conf.env.append_value('CXXFLAGS', flags)
0240             conf.env.append_unique('ENABLE_GCCDEPS', 'cxx')
0241 
0242 def options(opt):
0243     raise ValueError('Do not load gccdeps options')
0244