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 | |
---|