build: refactor the object reduction code
[samba.git] / buildtools / wafsamba / samba_deps.py
1 # Samba automatic dependency handling and project rules
2
3 import Build, os, re, Environment
4 from samba_utils import *
5 from samba_autoconf import *
6 from samba_bundled import BUILTIN_LIBRARY
7
8 @conf
9 def ADD_GLOBAL_DEPENDENCY(ctx, dep):
10     '''add a dependency for all binaries and libraries'''
11     if not 'GLOBAL_DEPENDENCIES' in ctx.env:
12         ctx.env.GLOBAL_DEPENDENCIES = []
13     ctx.env.GLOBAL_DEPENDENCIES.append(dep)
14
15
16 def TARGET_ALIAS(bld, target, alias):
17     '''define an alias for a target name'''
18     cache = LOCAL_CACHE(bld, 'TARGET_ALIAS')
19     if alias in cache:
20         print("Target alias %s already set to %s : newalias %s" % (alias, cache[alias], target))
21         raise
22     cache[alias] = target
23 Build.BuildContext.TARGET_ALIAS = TARGET_ALIAS
24
25
26 @conf
27 def SET_SYSLIB_DEPS(conf, target, deps):
28     '''setup some implied dependencies for a SYSLIB'''
29     cache = LOCAL_CACHE(conf, 'SYSLIB_DEPS')
30     cache[target] = deps
31
32
33 def EXPAND_ALIAS(bld, target):
34     '''expand a target name via an alias'''
35     aliases = LOCAL_CACHE(bld, 'TARGET_ALIAS')
36     if target in aliases:
37         return aliases[target]
38     return target
39 Build.BuildContext.EXPAND_ALIAS = EXPAND_ALIAS
40
41
42 def expand_subsystem_deps(bld):
43     '''expand the reverse dependencies resulting from subsystem
44        attributes of modules'''
45     subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
46     aliases    = LOCAL_CACHE(bld, 'TARGET_ALIAS')
47     targets    = LOCAL_CACHE(bld, 'TARGET_TYPE')
48
49     for s in subsystems:
50         if s in aliases:
51             s = aliases[s]
52         bld.ASSERT(s in targets, "Subsystem target %s not declared" % s)
53         type = targets[s]
54         if type == 'DISABLED' or type == 'EMPTY':
55             continue
56
57         t = bld.name_to_obj(s, bld.env)
58         bld.ASSERT(t is not None, "Subsystem target %s not found" % s)
59         for d in subsystems[s]:
60             type = targets[d['TARGET']]
61             if type != 'DISABLED' and type != 'EMPTY':
62                 t.samba_deps_extended.append(d['TARGET'])
63                 t2 = bld.name_to_obj(d['TARGET'], bld.env)
64                 t2.samba_includes_extended.extend(t.samba_includes_extended)
65                 t2.samba_deps_extended.extend(t.samba_deps_extended)
66         t.samba_deps_extended = unique_list(t.samba_deps_extended)
67
68
69
70 def build_dependencies(self):
71     '''This builds the dependency list for a target. It runs after all the targets are declared
72
73     The reason this is not just done in the SAMBA_*() rules is that we have no way of knowing
74     the full dependency list for a target until we have all of the targets declared.
75     '''
76
77     if self.samba_type in ['LIBRARY', 'BINARY', 'PYTHON']:
78         self.uselib        = list(self.final_syslibs)
79         self.uselib_local  = list(self.final_libs)
80         self.add_objects   = list(self.final_objects)
81
82         # extra link flags from pkg_config
83         libs = self.final_syslibs.copy()
84
85         (ccflags, ldflags) = library_flags(self, list(libs))
86         new_ldflags        = getattr(self, 'ldflags', [])
87         new_ldflags.extend(ldflags)
88         self.ldflags       = new_ldflags
89
90         debug('deps: computed dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
91               self.sname, self.uselib, self.uselib_local, self.add_objects)
92
93     if self.samba_type in ['SUBSYSTEM']:
94         # this is needed for the ccflags of libs that come from pkg_config
95         self.uselib = list(self.direct_syslibs)
96
97     if getattr(self, 'uselib', None):
98         up_list = []
99         for l in self.uselib:
100             up_list.append(l.upper())
101         self.uselib = up_list
102
103
104 def build_includes(self):
105     '''This builds the right set of includes for a target.
106
107     One tricky part of this is that the includes= attribute for a
108     target needs to use paths which are relative to that targets
109     declaration directory (which we can get at via t.path).
110
111     The way this works is the includes list gets added as
112     samba_includes in the main build task declaration. Then this
113     function runs after all of the tasks are declared, and it
114     processes the samba_includes attribute to produce a includes=
115     attribute
116     '''
117
118     if getattr(self, 'samba_includes', None) is None:
119         return
120
121     bld = self.bld
122
123     inc_deps = includes_objects(bld, self, set(), {})
124
125     includes = []
126
127     # maybe add local includes
128     if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
129         includes.append('.')
130
131     includes.extend(self.samba_includes_extended)
132
133     if 'EXTRA_INCLUDES' in bld.env:
134         includes.extend(bld.env['EXTRA_INCLUDES'])
135
136     includes.append('#')
137
138     inc_set = set()
139     inc_abs = []
140
141     for d in inc_deps:
142         t = bld.name_to_obj(d, bld.env)
143         bld.ASSERT(t is not None, "Unable to find dependency %s for %s" % (d, self.sname))
144         inclist = getattr(t, 'samba_includes_extended', [])
145         if getattr(t, 'local_include', True) == True:
146             inclist.append('.')
147         if inclist == []:
148             continue
149         tpath = t.samba_abspath
150         for inc in inclist:
151             npath = tpath + '/' + inc
152             if not npath in inc_set:
153                 inc_abs.append(npath)
154                 inc_set.add(npath)
155
156     mypath = self.path.abspath(bld.env)
157     for inc in inc_abs:
158         relpath = os_path_relpath(inc, mypath)
159         includes.append(relpath)
160
161     if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
162         includes.append('.')
163
164     # now transform the includes list to be relative to the top directory
165     # which is represented by '#' in waf. This allows waf to cache the
166     # includes lists more efficiently
167     includes_top = []
168     for i in includes:
169         if i[0] == '#':
170             # some are already top based
171             includes_top.append(i)
172             continue
173         absinc = os.path.join(self.path.abspath(), i)
174         relinc = os_path_relpath(absinc, self.bld.srcnode.abspath())
175         includes_top.append('#' + relinc)
176
177     self.includes = unique_list(includes_top)
178     debug('deps: includes for target %s: includes=%s',
179           self.sname, self.includes)
180
181
182
183
184 def add_init_functions(self):
185     '''This builds the right set of init functions'''
186
187     bld = self.bld
188
189     subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
190
191     # cope with the separated object lists from BINARY and LIBRARY targets
192     sname = self.sname
193     if sname.endswith('.objlist'):
194         sname = sname[0:-8]
195
196     modules = []
197     if sname in subsystems:
198         modules.append(sname)
199
200     m = getattr(self, 'samba_modules', None)
201     if m is not None:
202         modules.extend(TO_LIST(m))
203
204     m = getattr(self, 'samba_subsystem', None)
205     if m is not None:
206         modules.append(m)
207
208     if modules == []:
209         return
210
211     sentinal = getattr(self, 'init_function_sentinal', 'NULL')
212
213     targets    = LOCAL_CACHE(bld, 'TARGET_TYPE')
214
215     cflags = getattr(self, 'samba_cflags', [])[:]
216     for m in modules:
217         bld.ASSERT(m in subsystems,
218                    "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
219         init_fn_list = []
220         for d in subsystems[m]:
221             if targets[d['TARGET']] != 'DISABLED':
222                 init_fn_list.append(d['INIT_FUNCTION'])
223         if init_fn_list == []:
224             cflags.append('-DSTATIC_%s_MODULES=%s' % (m, sentinal))
225         else:
226             cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
227     self.ccflags = cflags
228
229
230
231 def check_duplicate_sources(bld, tgt_list):
232     '''see if we are compiling the same source file into multiple
233     subsystem targets for the same library or binary'''
234
235     debug('deps: checking for duplicate sources')
236
237     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
238     ret = True
239
240     seen = set()
241
242     for t in tgt_list:
243         obj_sources = getattr(t, 'source', '')
244         tpath = os_path_relpath(t.path.abspath(bld.env), t.env['BUILD_DIRECTORY'] + '/default')
245         obj_sources = bld.SUBDIR(tpath, obj_sources)
246         t.samba_source_set = set(TO_LIST(obj_sources))
247
248     for t in tgt_list:
249         if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
250             continue
251
252         sources = []
253         for obj in t.add_objects:
254             t2 = t.bld.name_to_obj(obj, bld.env)
255             source_set = getattr(t2, 'samba_source_set', set())
256             sources.append( { 'dep':obj, 'src':source_set} )
257         for s in sources:
258             for s2 in sources:
259                 if s['dep'] == s2['dep']: continue
260                 common = s['src'].intersection(s2['src'])
261                 if common.difference(seen):
262                     print("Target %s has duplicate source files in %s and %s : %s" % (t.sname,
263                                                                                       s['dep'], s2['dep'],
264                                                                                       common))
265                     seen = seen.union(common)
266                     ret = False
267     return ret
268
269
270 def check_orpaned_targets(bld, tgt_list):
271     '''check if any build targets are orphaned'''
272
273     target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
274
275     debug('deps: checking for orphaned targets')
276
277     for t in tgt_list:
278         if getattr(t, 'samba_used', False) == True:
279             continue
280         type = target_dict[t.sname]
281         if not type in ['BINARY', 'LIBRARY', 'MODULE', 'ET', 'PYTHON']:
282             if re.search('^PIDL_', t.sname) is None:
283                 print "Target %s of type %s is unused by any other target" % (t.sname, type)
284
285
286 def show_final_deps(bld, tgt_list):
287     '''show the final dependencies for all targets'''
288
289     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
290
291     for t in tgt_list:
292         if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
293             continue
294         debug('deps: final dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
295               t.sname, t.uselib, t.uselib_local, t.add_objects)
296
297
298 def add_samba_attributes(bld, tgt_list):
299     '''ensure a target has a the required samba attributes'''
300
301     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
302
303     for t in tgt_list:
304         if t.name != '':
305             t.sname = t.name
306         else:
307             t.sname = t.target
308         t.samba_type = targets[t.sname]
309         t.samba_abspath = t.path.abspath(bld.env)
310         t.samba_deps_extended = t.samba_deps[:]
311         t.samba_includes_extended = TO_LIST(t.samba_includes)[:]
312         t.ccflags = getattr(t, 'samba_cflags', '')
313
314
315 def build_direct_deps(bld, tgt_list):
316     '''build the direct_objects and direct_libs sets for each target'''
317
318     targets  = LOCAL_CACHE(bld, 'TARGET_TYPE')
319     syslib_deps  = LOCAL_CACHE(bld, 'SYSLIB_DEPS')
320     global_deps = bld.env.GLOBAL_DEPENDENCIES
321
322     for t in tgt_list:
323         t.direct_objects = set()
324         t.direct_libs = set()
325         t.direct_syslibs = set()
326         deps = t.samba_deps_extended
327         deps.extend(global_deps)
328         for d in deps:
329             d = EXPAND_ALIAS(bld, d)
330             if d == t.sname: continue
331             if not d in targets:
332                 print "Unknown dependency %s in %s" % (d, t.sname)
333                 raise
334             if targets[d] in [ 'EMPTY', 'DISABLED' ]:
335                 continue
336             if targets[d] == 'SYSLIB':
337                 t.direct_syslibs.add(d)
338                 if d in syslib_deps:
339                     for implied in TO_LIST(syslib_deps[d]):
340                         if BUILTIN_LIBRARY(bld, implied):
341                             t.direct_objects.add(implied)
342                         else:
343                             t.direct_libs.add(implied)
344                 continue
345             t2 = bld.name_to_obj(d, bld.env)
346             if t2 is None:
347                 print "no task %s type %s" % (d, targets[d])
348             if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
349                 t.direct_libs.add(d)
350             elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
351                 t.direct_objects.add(d)
352     debug('deps: built direct dependencies')
353
354
355 def dependency_loop(loops, t, target):
356     '''add a dependency loop to the loops dictionary'''
357     if t.sname == target:
358         return
359     if not target in loops:
360         loops[target] = set()
361     if not t.sname in loops[target]:
362         loops[target].add(t.sname)
363
364
365 def indirect_libs(bld, t, chain, loops):
366     '''recursively calculate the indirect library dependencies for a target
367
368     An indirect library is a library that results from a dependency on
369     a subsystem
370     '''
371
372     ret = getattr(t, 'indirect_libs', None)
373     if ret is not None:
374         return ret
375
376     ret = set()
377     for obj in t.direct_objects:
378         if obj in chain:
379             dependency_loop(loops, t, obj)
380             continue
381         chain.add(obj)
382         t2 = bld.name_to_obj(obj, bld.env)
383         r2 = indirect_libs(bld, t2, chain, loops)
384         chain.remove(obj)
385         ret = ret.union(t2.direct_libs)
386         ret = ret.union(r2)
387
388     for obj in indirect_objects(bld, t, set(), loops):
389         if obj in chain:
390             dependency_loop(loops, t, obj)
391             continue
392         chain.add(obj)
393         t2 = bld.name_to_obj(obj, bld.env)
394         r2 = indirect_libs(bld, t2, chain, loops)
395         chain.remove(obj)
396         ret = ret.union(t2.direct_libs)
397         ret = ret.union(r2)
398
399     t.indirect_libs = ret
400
401     return ret
402
403
404 def indirect_objects(bld, t, chain, loops):
405     '''recursively calculate the indirect object dependencies for a target
406
407     indirect objects are the set of objects from expanding the
408     subsystem dependencies
409     '''
410
411     ret = getattr(t, 'indirect_objects', None)
412     if ret is not None: return ret
413
414     ret = set()
415     for lib in t.direct_objects:
416         if lib in chain:
417             dependency_loop(loops, t, lib)
418             continue
419         chain.add(lib)
420         t2 = bld.name_to_obj(lib, bld.env)
421         r2 = indirect_objects(bld, t2, chain, loops)
422         chain.remove(lib)
423         ret = ret.union(t2.direct_objects)
424         ret = ret.union(r2)
425
426     t.indirect_objects = ret
427     return ret
428
429
430 def extended_objects(bld, t, chain):
431     '''recursively calculate the extended object dependencies for a target
432
433     extended objects are the union of:
434        - direct objects
435        - indirect objects
436        - direct and indirect objects of all direct and indirect libraries
437     '''
438
439     ret = getattr(t, 'extended_objects', None)
440     if ret is not None: return ret
441
442     ret = set()
443     ret = ret.union(t.final_objects)
444
445     for lib in t.final_libs:
446         if lib in chain:
447             continue
448         t2 = bld.name_to_obj(lib, bld.env)
449         chain.add(lib)
450         r2 = extended_objects(bld, t2, chain)
451         chain.remove(lib)
452         ret = ret.union(t2.final_objects)
453         ret = ret.union(r2)
454
455     t.extended_objects = ret
456     return ret
457
458
459 def includes_objects(bld, t, chain, inc_loops):
460     '''recursively calculate the includes object dependencies for a target
461
462     includes dependencies come from either library or object dependencies
463     '''
464     ret = getattr(t, 'includes_objects', None)
465     if ret is not None:
466         return ret
467
468     ret = t.direct_objects.copy()
469     ret = ret.union(t.direct_libs)
470
471     for obj in t.direct_objects:
472         if obj in chain:
473             dependency_loop(inc_loops, t, obj)
474             continue
475         chain.add(obj)
476         t2 = bld.name_to_obj(obj, bld.env)
477         r2 = includes_objects(bld, t2, chain, inc_loops)
478         chain.remove(obj)
479         ret = ret.union(t2.direct_objects)
480         ret = ret.union(r2)
481
482     for lib in t.direct_libs:
483         if lib in chain:
484             dependency_loop(inc_loops, t, lib)
485             continue
486         chain.add(lib)
487         t2 = bld.name_to_obj(lib, bld.env)
488         r2 = includes_objects(bld, t2, chain, inc_loops)
489         chain.remove(lib)
490         ret = ret.union(t2.direct_objects)
491         ret = ret.union(r2)
492
493     t.includes_objects = ret
494     return ret
495
496
497 def break_dependency_loops(bld, tgt_list):
498     '''find and break dependency loops'''
499     loops = {}
500     inc_loops = {}
501
502     # build up the list of loops
503     for t in tgt_list:
504         indirect_objects(bld, t, set(), loops)
505         indirect_libs(bld, t, set(), loops)
506         includes_objects(bld, t, set(), inc_loops)
507
508     # break the loops
509     for t in tgt_list:
510         if t.sname in loops:
511             for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
512                 objs = getattr(t, attr, set())
513                 setattr(t, attr, objs.difference(loops[t.sname]))
514
515     for loop in loops:
516         debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
517
518     for loop in inc_loops:
519         debug('deps: Found include loops for target %s : %s', loop, inc_loops[loop])
520
521     # expand the loops mapping by one level
522     for loop in loops.copy():
523         for tgt in loops[loop]:
524             if tgt in loops:
525                 loops[loop] = loops[loop].union(loops[tgt])
526
527     for loop in inc_loops.copy():
528         for tgt in inc_loops[loop]:
529             if tgt in inc_loops:
530                 inc_loops[loop] = inc_loops[loop].union(inc_loops[tgt])
531
532
533     # expand indirect subsystem and library loops
534     for loop in loops.copy():
535         t = bld.name_to_obj(loop, bld.env)
536         if t.samba_type in ['SUBSYSTEM']:
537             loops[loop] = loops[loop].union(t.indirect_objects)
538             loops[loop] = loops[loop].union(t.direct_objects)
539         if t.samba_type in ['LIBRARY','PYTHON']:
540             loops[loop] = loops[loop].union(t.indirect_libs)
541             loops[loop] = loops[loop].union(t.direct_libs)
542         if loop in loops[loop]:
543             loops[loop].remove(loop)
544
545     # expand indirect includes loops
546     for loop in inc_loops.copy():
547         t = bld.name_to_obj(loop, bld.env)
548         inc_loops[loop] = inc_loops[loop].union(t.includes_objects)
549         if loop in inc_loops[loop]:
550             inc_loops[loop].remove(loop)
551
552     # add in the replacement dependencies
553     for t in tgt_list:
554         for loop in loops:
555             for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
556                 objs = getattr(t, attr, set())
557                 if loop in objs:
558                     diff = loops[loop].difference(objs)
559                     if t.sname in diff:
560                         diff.remove(t.sname)
561                     if diff:
562                         debug('deps: Expanded target %s of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
563                         objs = objs.union(diff)
564                 setattr(t, attr, objs)
565
566         for loop in inc_loops:
567             objs = getattr(t, 'includes_objects', set())
568             if loop in objs:
569                 diff = inc_loops[loop].difference(objs)
570                 if t.sname in diff:
571                     diff.remove(t.sname)
572                 if diff:
573                     debug('deps: Expanded target %s includes of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
574                     objs = objs.union(diff)
575             setattr(t, 'includes_objects', objs)
576
577
578 def reduce_objects(bld, tgt_list):
579     '''reduce objects by looking for indirect object dependencies'''
580     rely_on = {}
581
582     for t in tgt_list:
583         t.extended_objects = None
584
585     for type in ['BINARY', 'PYTHON', 'LIBRARY']:
586         for t in tgt_list:
587             if t.samba_type != type: continue
588             # if we will indirectly link to a target then we don't need it
589             new = t.final_objects.copy()
590             for l in t.final_libs:
591                 t2 = bld.name_to_obj(l, bld.env)
592                 t2_obj = extended_objects(bld, t2, set())
593                 dup = new.intersection(t2_obj)
594                 if dup:
595                     debug('deps: removing dups from %s of type %s: %s also in %s %s',
596                           t.sname, t.samba_type, dup, t2.samba_type, l)
597                     new = new.difference(dup)
598                     changed = True
599                     if not l in rely_on:
600                         rely_on[l] = set()
601                     rely_on[l] = rely_on[l].union(dup)
602             t.final_objects = new
603
604     # add back in any objects that were relied upon by the reduction rules
605     for r in rely_on:
606         t = bld.name_to_obj(r, bld.env)
607         t.final_objects = t.final_objects.union(rely_on[r])
608
609
610 def calculate_final_deps(bld, tgt_list, loops):
611     '''calculate the final library and object dependencies'''
612     for t in tgt_list:
613         # start with the maximum possible list
614         t.final_libs    = t.direct_libs.union(indirect_libs(bld, t, set(), loops))
615         t.final_objects = t.direct_objects.union(indirect_objects(bld, t, set(), loops))
616
617     for t in tgt_list:
618         # don't depend on ourselves
619         if t.sname in t.final_libs:
620             t.final_libs.remove(t.sname)
621         if t.sname in t.final_objects:
622             t.final_objects.remove(t.sname)
623
624     # find any library loops
625     for t in tgt_list:
626         if t.samba_type in ['LIBRARY', 'PYTHON']:
627             for l in t.final_libs.copy():
628                 t2 = bld.name_to_obj(l, bld.env)
629                 if t.sname in t2.final_libs:
630                     # we could break this in either direction. If one of the libraries
631                     # has a version number, and will this be distributed publicly, then
632                     # we should make it the lower level library in the DAG
633                     debug('deps: removing library loop %s from %s', t.sname, t2.sname)
634                     dependency_loop(loops, t, t2.sname)
635                     t2.final_libs.remove(t.sname)
636
637
638     for loop in loops:
639         debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
640
641     # we now need to make corrections for any library loops we broke up
642     # any target that depended on the target of the loop and doesn't
643     # depend on the source of the loop needs to get the loop source added
644     for type in ['BINARY','PYTHON','LIBRARY','BINARY']:
645         for t in tgt_list:
646             if t.samba_type != type: continue
647             for loop in loops:
648                 if loop in t.final_libs:
649                     diff = loops[loop].difference(t.final_libs)
650                     if t.sname in diff:
651                         diff.remove(t.sname)
652                     if diff:
653                         debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
654                         t.final_libs = t.final_libs.union(diff)
655
656     # remove objects that are also available in linked libs
657     reduce_objects(bld, tgt_list)
658
659     # add in any syslib dependencies
660     for t in tgt_list:
661         if not t.samba_type in ['BINARY','PYTHON','LIBRARY']:
662             continue
663         syslibs = set()
664         for d in t.final_objects:
665             t2 = bld.name_to_obj(d, bld.env)
666             syslibs = syslibs.union(t2.direct_syslibs)
667         # this adds the indirect syslibs as well, which may not be needed
668         # depending on the linker flags
669         for d in t.final_libs:
670             t2 = bld.name_to_obj(d, bld.env)
671             syslibs = syslibs.union(t2.direct_syslibs)
672         t.final_syslibs = syslibs
673
674     debug('deps: removed duplicate dependencies')
675
676
677
678 ######################################################################
679 # this provides a way to save our dependency calculations between runs
680 savedeps_version = 3
681 savedeps_inputs  = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
682 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
683 savedeps_outenv  = ['INC_PATHS']
684 savedeps_caches  = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS', 'SYSLIB_DEPS']
685 savedeps_files   = ['buildtools/wafsamba/samba_deps.py']
686
687 def save_samba_deps(bld, tgt_list):
688     '''save the dependency calculations between builds, to make
689        further builds faster'''
690     denv = Environment.Environment()
691
692     denv.version = savedeps_version
693     denv.savedeps_inputs = savedeps_inputs
694     denv.savedeps_outputs = savedeps_outputs
695     denv.input = {}
696     denv.output = {}
697     denv.outenv = {}
698     denv.caches = {}
699     denv.files  = {}
700
701     for f in savedeps_files:
702         denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
703
704     for c in savedeps_caches:
705         denv.caches[c] = LOCAL_CACHE(bld, c)
706
707     for t in tgt_list:
708         # save all the input attributes for each target
709         tdeps = {}
710         for attr in savedeps_inputs:
711             v = getattr(t, attr, None)
712             if v is not None:
713                 tdeps[attr] = v
714         if tdeps != {}:
715             denv.input[t.sname] = tdeps
716
717         # save all the output attributes for each target
718         tdeps = {}
719         for attr in savedeps_outputs:
720             v = getattr(t, attr, None)
721             if v is not None:
722                 tdeps[attr] = v
723         if tdeps != {}:
724             denv.output[t.sname] = tdeps
725
726         tdeps = {}
727         for attr in savedeps_outenv:
728             if attr in t.env:
729                 tdeps[attr] = t.env[attr]
730         if tdeps != {}:
731             denv.outenv[t.sname] = tdeps
732
733     depsfile = os.path.join(bld.bdir, "sambadeps")
734     denv.store(depsfile)
735
736
737 def load_samba_deps(bld, tgt_list):
738     '''load a previous set of build dependencies if possible'''
739     depsfile = os.path.join(bld.bdir, "sambadeps")
740     denv = Environment.Environment()
741     try:
742         debug('deps: checking saved dependencies')
743         denv.load(depsfile)
744         if (denv.version != savedeps_version or
745             denv.savedeps_inputs != savedeps_inputs or
746             denv.savedeps_outputs != savedeps_outputs):
747             return False
748     except:
749         return False
750
751     # check if critical files have changed
752     for f in savedeps_files:
753         if f not in denv.files:
754             return False
755         if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
756             return False
757
758     # check if caches are the same
759     for c in savedeps_caches:
760         if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
761             return False
762
763     # check inputs are the same
764     for t in tgt_list:
765         tdeps = {}
766         for attr in savedeps_inputs:
767             v = getattr(t, attr, None)
768             if v is not None:
769                 tdeps[attr] = v
770         if t.sname in denv.input:
771             olddeps = denv.input[t.sname]
772         else:
773             olddeps = {}
774         if tdeps != olddeps:
775             #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
776             return False
777
778     # put outputs in place
779     for t in tgt_list:
780         if not t.sname in denv.output: continue
781         tdeps = denv.output[t.sname]
782         for a in tdeps:
783             setattr(t, a, tdeps[a])
784
785     # put output env vars in place
786     for t in tgt_list:
787         if not t.sname in denv.outenv: continue
788         tdeps = denv.outenv[t.sname]
789         for a in tdeps:
790             t.env[a] = tdeps[a]
791
792     debug('deps: loaded saved dependencies')
793     return True
794
795
796 def check_project_rules(bld):
797     '''check the project rules - ensuring the targets are sane'''
798
799     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
800     loops = {}
801     inc_loops = {}
802
803     # build a list of task generators we are interested in
804     tgt_list = []
805     for tgt in targets:
806         type = targets[tgt]
807         if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
808             continue
809         t = bld.name_to_obj(tgt, bld.env)
810         if t is None:
811             print "Target %s of type %s has no task generator" % (tgt, type)
812             raise
813         tgt_list.append(t)
814
815     add_samba_attributes(bld, tgt_list)
816
817     if load_samba_deps(bld, tgt_list):
818         return
819
820     print "Checking project rules ..."
821
822     debug('deps: project rules checking started')
823
824     expand_subsystem_deps(bld)
825     build_direct_deps(bld, tgt_list)
826     break_dependency_loops(bld, tgt_list)
827     calculate_final_deps(bld, tgt_list, loops)
828
829     # run the various attribute generators
830     for f in [ build_dependencies, build_includes, add_init_functions ]:
831         debug('deps: project rules checking %s', f)
832         for t in tgt_list: f(t)
833
834     debug('deps: project rules stage1 completed')
835
836     #check_orpaned_targets(bld, tgt_list)
837
838     if not check_duplicate_sources(bld, tgt_list):
839         print "Duplicate sources present - aborting"
840         sys.exit(1)
841
842     show_final_deps(bld, tgt_list)
843
844     debug('deps: project rules checking completed - %u targets checked',
845           len(tgt_list))
846
847     save_samba_deps(bld, tgt_list)
848
849     print "Project rules pass"
850
851
852 def CHECK_PROJECT_RULES(bld):
853     '''enable checking of project targets for sanity'''
854     if bld.env.added_project_rules:
855         return
856     bld.env.added_project_rules = True
857     bld.add_pre_fun(check_project_rules)
858 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES
859
860