Reduce the no-op build times by 30%
[obnox/samba/samba-obnox.git] / buildtools / wafsamba / samba_optimisation.py
1 # This file contains waf optimisations for Samba
2
3 # most of these optimisations are possible because of the restricted build environment
4 # that Samba has. For example, Samba doesn't attempt to cope with Win32 paths during the
5 # build, and Samba doesn't need build varients
6
7 # overall this makes some build tasks quite a bit faster
8
9 import Build, Utils, Node
10 from TaskGen import feature, after
11 import preproc, Task
12
13 @feature('cc', 'cxx')
14 @after('apply_type_vars', 'apply_lib_vars', 'apply_core')
15 def apply_incpaths(self):
16     lst = []
17
18     try:
19         kak = self.bld.kak
20     except AttributeError:
21         kak = self.bld.kak = {}
22
23     # TODO move the uselib processing out of here
24     for lib in self.to_list(self.uselib):
25         for path in self.env['CPPPATH_' + lib]:
26             if not path in lst:
27                 lst.append(path)
28     if preproc.go_absolute:
29         for path in preproc.standard_includes:
30             if not path in lst:
31                 lst.append(path)
32
33     for path in self.to_list(self.includes):
34         if not path in lst:
35             if preproc.go_absolute or path[0] != '/':  # os.path.isabs(path):
36                 lst.append(path)
37             else:
38                 self.env.prepend_value('CPPPATH', path)
39
40     for path in lst:
41         node = None
42         if path[0] == '/': # os.path.isabs(path):
43             if preproc.go_absolute:
44                 node = self.bld.root.find_dir(path)
45         elif path[0] == '#':
46             node = self.bld.srcnode
47             if len(path) > 1:
48                 try:
49                     node = kak[path]
50                 except KeyError:
51                     kak[path] = node = node.find_dir(path[1:])
52         else:
53             try:
54                 node = kak[(self.path.id, path)]
55             except KeyError:
56                 kak[(self.path.id, path)] = node = self.path.find_dir(path)
57
58         if node:
59             self.env.append_value('INC_PATHS', node)
60
61 @feature('cc')
62 @after('apply_incpaths')
63 def apply_obj_vars_cc(self):
64     """after apply_incpaths for INC_PATHS"""
65     env = self.env
66     app = env.append_unique
67     cpppath_st = env['CPPPATH_ST']
68
69     lss = env['_CCINCFLAGS']
70
71     try:
72          cac = self.bld.cac
73     except AttributeError:
74          cac = self.bld.cac = {}
75
76     # local flags come first
77     # set the user-defined includes paths
78     for i in env['INC_PATHS']:
79
80         try:
81             lss.extend(cac[i.id])
82         except KeyError:
83
84             cac[i.id] = [cpppath_st % i.bldpath(env), cpppath_st % i.srcpath(env)]
85             lss.extend(cac[i.id])
86
87     env['_CCINCFLAGS'] = lss
88     # set the library include paths
89     for i in env['CPPPATH']:
90         app('_CCINCFLAGS', cpppath_st % i)
91
92 import Node, Environment
93
94 def vari(self):
95     return "default"
96 Environment.Environment.variant = vari
97
98 def variant(self, env):
99     if not env: return 0
100     elif self.id & 3 == Node.FILE: return 0
101     else: return "default"
102 Node.Node.variant = variant
103
104
105 import TaskGen, Task
106
107 def create_task(self, name, src=None, tgt=None):
108     task = Task.TaskBase.classes[name](self.env, generator=self)
109     if src:
110         task.set_inputs(src)
111     if tgt:
112         task.set_outputs(tgt)
113     return task
114 TaskGen.task_gen.create_task = create_task
115
116 def hash_constraints(self):
117     a = self.attr
118     sum = hash((str(a('before', '')),
119             str(a('after', '')),
120             str(a('ext_in', '')),
121             str(a('ext_out', '')),
122             self.__class__.maxjobs))
123     return sum
124 Task.TaskBase.hash_constraints = hash_constraints
125
126
127 # import cc
128 # from TaskGen import extension
129 # import Utils
130
131 # @extension(cc.EXT_CC)
132 # def c_hook(self, node):
133 #     task = self.create_task('cc', node, node.change_ext('.o'))
134 #     try:
135 #         self.compiled_tasks.append(task)
136 #     except AttributeError:
137 #         raise Utils.WafError('Have you forgotten to set the feature "cc" on %s?' % str(self))
138
139 #     bld = self.bld
140 #     try:
141 #         dc = bld.dc
142 #     except AttributeError:
143 #         dc = bld.dc = {}
144
145 #     if task.outputs[0].id in dc:
146 #         raise Utils.WafError('Samba, you are doing it wrong %r %s %s' % (task.outputs, task.generator, dc[task.outputs[0].id].generator))
147 #     else:
148 #         dc[task.outputs[0].id] = task
149
150 #     return task
151
152
153 def suncc_wrap(cls):
154     '''work around a problem with cc on solaris not handling module aliases
155     which have empty libs'''
156     if getattr(cls, 'solaris_wrap', False):
157         return
158     cls.solaris_wrap = True
159     oldrun = cls.run
160     def run(self):
161         if self.env.CC_NAME == "sun" and not self.inputs:
162             self.env = self.env.copy()
163             self.env.append_value('LINKFLAGS', '-')
164         return oldrun(self)
165     cls.run = run
166 suncc_wrap(Task.TaskBase.classes['cc_link'])
167
168
169
170 def hash_env_vars(self, env, vars_lst):
171     idx = str(id(env)) + str(vars_lst)
172     try:
173         return self.cache_sig_vars[idx]
174     except KeyError:
175         pass
176
177     m = Utils.md5()
178     m.update(''.join([str(env[a]) for a in vars_lst]))
179
180     ret = self.cache_sig_vars[idx] = m.digest()
181     return ret
182 Build.BuildContext.hash_env_vars = hash_env_vars
183
184
185 def store_fast(self, filename):
186     file = open(filename, 'wb')
187     data = self.get_merged_dict()
188     try:
189         Build.cPickle.dump(data, file, -1)
190     finally:
191         file.close()
192 Environment.Environment.store_fast = store_fast
193
194 def load_fast(self, filename):
195     file = open(filename, 'rb')
196     try:
197         data = Build.cPickle.load(file)
198     finally:
199         file.close()
200     self.table.update(data)
201 Environment.Environment.load_fast = load_fast
202
203 def is_this_a_static_lib(self, name):
204     try:
205         cache = self.cache_is_this_a_static_lib
206     except AttributeError:
207         cache = self.cache_is_this_a_static_lib = {}
208     try:
209         return cache[name]
210     except KeyError:
211         ret = cache[name] = 'cstaticlib' in self.bld.name_to_obj(name, self.env).features
212         return ret
213 TaskGen.task_gen.is_this_a_static_lib = is_this_a_static_lib
214
215 def shared_ancestors(self):
216     try:
217         cache = self.cache_is_this_a_static_lib
218     except AttributeError:
219         cache = self.cache_is_this_a_static_lib = {}
220     try:
221         return cache[id(self)]
222     except KeyError:
223
224         ret = []
225         if 'cshlib' in self.features: # or 'cprogram' in self.features:
226             if getattr(self, 'uselib_local', None):
227                 lst = self.to_list(self.uselib_local)
228                 ret = [x for x in lst if not self.is_this_a_static_lib(x)]
229         cache[id(self)] = ret
230         return ret
231 TaskGen.task_gen.shared_ancestors = shared_ancestors
232
233 @feature('cc', 'cxx')
234 @after('apply_link', 'init_cc', 'init_cxx', 'apply_core')
235 def apply_lib_vars(self):
236     """after apply_link because of 'link_task'
237     after default_cc because of the attribute 'uselib'"""
238
239     # after 'apply_core' in case if 'cc' if there is no link
240
241     env = self.env
242     app = env.append_value
243     seen_libpaths = set([])
244
245     # OPTIMIZATION 1: skip uselib variables already added (700ms)
246     seen_uselib = set([])
247
248     # 1. the case of the libs defined in the project (visit ancestors first)
249     # the ancestors external libraries (uselib) will be prepended
250     self.uselib = self.to_list(self.uselib)
251     names = self.to_list(self.uselib_local)
252
253     seen = set([])
254     tmp = Utils.deque(names) # consume a copy of the list of names
255     while tmp:
256         lib_name = tmp.popleft()
257         # visit dependencies only once
258         if lib_name in seen:
259             continue
260
261         y = self.name_to_obj(lib_name)
262         if not y:
263             raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name))
264         y.post()
265         seen.add(lib_name)
266
267         # OPTIMIZATION 2: pre-compute ancestors shared libraries (100ms)
268         tmp.extend(y.shared_ancestors())
269
270         # link task and flags
271         if getattr(y, 'link_task', None):
272
273             link_name = y.target[y.target.rfind('/') + 1:]
274             if 'cstaticlib' in y.features:
275                 app('STATICLIB', link_name)
276             elif 'cshlib' in y.features or 'cprogram' in y.features:
277                 # WARNING some linkers can link against programs
278                 app('LIB', link_name)
279
280             # the order
281             self.link_task.set_run_after(y.link_task)
282
283             # for the recompilation
284             dep_nodes = getattr(self.link_task, 'dep_nodes', [])
285             self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
286
287             # OPTIMIZATION 3: reduce the amount of function calls
288             # add the link path too
289             par = y.link_task.outputs[0].parent
290             if id(par) not in seen_libpaths:
291                 seen_libpaths.add(id(par))
292                 tmp_path = par.bldpath(self.env)
293                 if not tmp_path in env['LIBPATH']:
294                     env.prepend_value('LIBPATH', tmp_path)
295
296
297         # add ancestors uselib too - but only propagate those that have no staticlib
298         for v in self.to_list(y.uselib):
299             if v not in seen_uselib:
300                 seen_uselib.add(v)
301                 if not env['STATICLIB_' + v]:
302                     if not v in self.uselib:
303                         self.uselib.insert(0, v)
304
305     # 2. the case of the libs defined outside
306     for x in self.uselib:
307         for v in self.p_flag_vars:
308             val = self.env[v + '_' + x]
309             if val:
310                 self.env.append_value(v, val)