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