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