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