[f3f0370f] | 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 c_preproc |
---|
| 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 | def 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 | |
---|
| 39 | re_o = re.compile(r"\.o$") |
---|
| 40 | re_splitter = re.compile(r'(?<!\\)\s+') # split by space, except when spaces are escaped |
---|
| 41 | |
---|
| 42 | def 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 | |
---|
| 55 | def 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 | |
---|
| 74 | def 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 | |
---|
| 163 | def 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 | bld = self.generator.bld |
---|
| 167 | |
---|
| 168 | try: |
---|
| 169 | return self.compute_sig_implicit_deps() |
---|
| 170 | except Errors.TaskNotReady: |
---|
| 171 | raise ValueError("Please specify the build order precisely with gccdeps (asm/c/c++ tasks)") |
---|
| 172 | except EnvironmentError: |
---|
| 173 | # If a file is renamed, assume the dependencies are stale and must be recalculated |
---|
| 174 | for x in bld.node_deps.get(self.uid(), []): |
---|
| 175 | if not x.is_bld() and not x.exists(): |
---|
| 176 | try: |
---|
| 177 | del x.parent.children[x.name] |
---|
| 178 | except KeyError: |
---|
| 179 | pass |
---|
| 180 | |
---|
| 181 | key = self.uid() |
---|
| 182 | bld.node_deps[key] = [] |
---|
| 183 | bld.raw_deps[key] = [] |
---|
| 184 | return Utils.SIG_NIL |
---|
| 185 | |
---|
| 186 | def wrap_compiled_task(classname): |
---|
| 187 | derived_class = type(classname, (Task.classes[classname],), {}) |
---|
| 188 | derived_class.derived_gccdeps = derived_class |
---|
| 189 | derived_class.post_run = post_run |
---|
| 190 | derived_class.scan = scan |
---|
| 191 | derived_class.sig_implicit_deps = sig_implicit_deps |
---|
| 192 | |
---|
| 193 | for k in ('asm', 'c', 'cxx'): |
---|
| 194 | if k in Task.classes: |
---|
| 195 | wrap_compiled_task(k) |
---|
| 196 | |
---|
| 197 | @before_method('process_source') |
---|
| 198 | @feature('force_gccdeps') |
---|
| 199 | def force_gccdeps(self): |
---|
| 200 | self.env.ENABLE_GCCDEPS = ['asm', 'c', 'cxx'] |
---|
| 201 | |
---|
| 202 | def configure(conf): |
---|
| 203 | # in case someone provides a --enable-gccdeps command-line option |
---|
| 204 | if not getattr(conf.options, 'enable_gccdeps', True): |
---|
| 205 | return |
---|
| 206 | |
---|
| 207 | global gccdeps_flags |
---|
| 208 | flags = conf.env.GCCDEPS_FLAGS or gccdeps_flags |
---|
| 209 | if conf.env.ASM_NAME in supported_compilers: |
---|
| 210 | try: |
---|
| 211 | conf.check(fragment='', features='asm force_gccdeps', asflags=flags, compile_filename='test.S', msg='Checking for asm flags %r' % ''.join(flags)) |
---|
| 212 | except Errors.ConfigurationError: |
---|
| 213 | pass |
---|
| 214 | else: |
---|
| 215 | conf.env.append_value('ASFLAGS', flags) |
---|
| 216 | conf.env.append_unique('ENABLE_GCCDEPS', 'asm') |
---|
| 217 | |
---|
| 218 | if conf.env.CC_NAME in supported_compilers: |
---|
| 219 | try: |
---|
| 220 | conf.check(fragment='int main() { return 0; }', features='c force_gccdeps', cflags=flags, msg='Checking for c flags %r' % ''.join(flags)) |
---|
| 221 | except Errors.ConfigurationError: |
---|
| 222 | pass |
---|
| 223 | else: |
---|
| 224 | conf.env.append_value('CFLAGS', flags) |
---|
| 225 | conf.env.append_unique('ENABLE_GCCDEPS', 'c') |
---|
| 226 | |
---|
| 227 | if conf.env.CXX_NAME in supported_compilers: |
---|
| 228 | try: |
---|
| 229 | conf.check(fragment='int main() { return 0; }', features='cxx force_gccdeps', cxxflags=flags, msg='Checking for cxx flags %r' % ''.join(flags)) |
---|
| 230 | except Errors.ConfigurationError: |
---|
| 231 | pass |
---|
| 232 | else: |
---|
| 233 | conf.env.append_value('CXXFLAGS', flags) |
---|
| 234 | conf.env.append_unique('ENABLE_GCCDEPS', 'cxx') |
---|
| 235 | |
---|
| 236 | def options(opt): |
---|
| 237 | raise ValueError('Do not load gccdeps options') |
---|
| 238 | |
---|