1 # Samba automatic dependency handling and project rules
3 import Build, os, re, Environment
4 from samba_utils import *
5 from samba_autoconf import *
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)
15 def TARGET_ALIAS(bld, target, alias):
16 '''define an alias for a target name'''
17 cache = LOCAL_CACHE(bld, 'TARGET_ALIAS')
19 print("Target alias %s already set to %s : newalias %s" % (alias, cache[alias], target))
22 Build.BuildContext.TARGET_ALIAS = TARGET_ALIAS
25 def EXPAND_ALIAS(bld, target):
26 '''expand a target name via an alias'''
27 aliases = LOCAL_CACHE(bld, 'TARGET_ALIAS')
29 return aliases[target]
31 Build.BuildContext.EXPAND_ALIAS = EXPAND_ALIAS
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')
44 bld.ASSERT(s in targets, "Subsystem target %s not declared" % s)
46 if type == 'DISABLED' or type == 'EMPTY':
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)
62 def build_dependencies(self):
63 '''This builds the dependency list for a target. It runs after all the targets are declared
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.
69 # we only should add extra library and object deps on libraries and binaries
70 if not self.samba_type in ['LIBRARY', 'BINARY', 'PYTHON']:
73 # we need to link against:
75 # 1) any direct system libs
76 # 2) any indirect system libs that come from subsystem dependencies
77 # 3) any direct local libs
78 # 4) any indirect local libs that come from subsystem dependencies
79 # 5) any direct objects
80 # 6) any indirect objects that come from subsystem dependencies
82 self.uselib = list(self.final_syslibs)
83 self.uselib_local = list(self.final_libs)
84 self.add_objects = list(self.final_objects)
86 debug('deps: computed dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
87 self.sname, self.uselib, self.uselib_local, self.add_objects)
91 def build_includes(self):
92 '''This builds the right set of includes for a target.
94 One tricky part of this is that the includes= attribute for a
95 target needs to use paths which are relative to that targets
96 declaration directory (which we can get at via t.path).
98 The way this works is the includes list gets added as
99 samba_includes in the main build task declaration. Then this
100 function runs after all of the tasks are declared, and it
101 processes the samba_includes attribute to produce a includes=
105 if getattr(self, 'samba_includes', None) is None:
110 inc_deps = includes_objects(bld, self, set(), {})
114 # maybe add local includes
115 if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
118 includes.extend(self.samba_includes_extended)
120 if 'EXTRA_INCLUDES' in bld.env:
121 includes.extend(bld.env['EXTRA_INCLUDES'])
129 t = bld.name_to_obj(d, bld.env)
130 bld.ASSERT(t is not None, "Unable to find dependency %s for %s" % (d, self.sname))
131 inclist = getattr(t, 'samba_includes_extended', [])
132 if getattr(t, 'local_include', True) == True:
136 tpath = t.samba_abspath
138 npath = tpath + '/' + inc
139 if not npath in inc_set:
140 inc_abs.append(npath)
143 mypath = self.path.abspath(bld.env)
145 relpath = os_path_relpath(inc, mypath)
146 includes.append(relpath)
148 if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
151 # now transform the includes list to be relative to the top directory
152 # which is represented by '#' in waf. This allows waf to cache the
153 # includes lists more efficiently
157 # some are already top based
158 includes_top.append(i)
160 absinc = os.path.join(self.path.abspath(), i)
161 relinc = os_path_relpath(absinc, self.bld.srcnode.abspath())
162 includes_top.append('#' + relinc)
164 self.includes = unique_list(includes_top)
165 debug('deps: includes for target %s: includes=%s',
166 self.sname, self.includes)
171 def add_init_functions(self):
172 '''This builds the right set of init functions'''
176 subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
178 # cope with the separated object lists from BINARY and LIBRARY targets
180 if sname.endswith('.objlist'):
184 if sname in subsystems:
185 modules.append(sname)
187 m = getattr(self, 'samba_modules', None)
189 modules.extend(TO_LIST(m))
191 m = getattr(self, 'samba_subsystem', None)
198 sentinal = getattr(self, 'init_function_sentinal', 'NULL')
200 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
202 cflags = getattr(self, 'samba_cflags', [])[:]
204 bld.ASSERT(m in subsystems,
205 "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
207 for d in subsystems[m]:
208 if targets[d['TARGET']] != 'DISABLED':
209 init_fn_list.append(d['INIT_FUNCTION'])
210 if init_fn_list == []:
211 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, sentinal))
213 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
214 self.ccflags = cflags
218 def check_duplicate_sources(bld, tgt_list):
219 '''see if we are compiling the same source file into multiple
220 subsystem targets for the same library or binary'''
222 debug('deps: checking for duplicate sources')
224 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
230 # this was useful for finding problems with the autogenerated rules
233 sources = TO_LIST(getattr(t, 'source', ''))
235 bname = os.path.basename(s)
236 if bname in base_list:
237 print "Suspicious duplicate name %s in %s" % (bname, t.sname)
244 obj_sources = getattr(t, 'source', '')
245 tpath = os_path_relpath(t.path.abspath(bld.env), t.env['BUILD_DIRECTORY'] + '/default')
246 obj_sources = bld.SUBDIR(tpath, obj_sources)
247 t.samba_source_set = set(TO_LIST(obj_sources))
250 if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
254 for obj in t.add_objects:
255 t2 = t.bld.name_to_obj(obj, bld.env)
256 source_set = getattr(t2, 'samba_source_set', set())
257 sources.append( { 'dep':obj, 'src':source_set} )
260 if s['dep'] == s2['dep']: continue
261 common = s['src'].intersection(s2['src'])
262 if common.difference(seen):
263 print("Target %s has duplicate source files in %s and %s : %s" % (t.sname,
266 seen = seen.union(common)
271 def check_orpaned_targets(bld, tgt_list):
272 '''check if any build targets are orphaned'''
274 target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
276 debug('deps: checking for orphaned targets')
279 if getattr(t, 'samba_used', False) == True:
281 type = target_dict[t.sname]
282 if not type in ['BINARY', 'LIBRARY', 'MODULE', 'ET', 'PYTHON']:
283 if re.search('^PIDL_', t.sname) is None:
284 print "Target %s of type %s is unused by any other target" % (t.sname, type)
287 def show_final_deps(bld, tgt_list):
288 '''show the final dependencies for all targets'''
290 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
293 if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
295 debug('deps: final dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
296 t.sname, t.uselib, t.uselib_local, t.add_objects)
299 def add_samba_attributes(bld, tgt_list):
300 '''ensure a target has a the required samba attributes'''
302 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
309 t.samba_type = targets[t.sname]
310 t.samba_abspath = t.path.abspath(bld.env)
311 t.samba_deps_extended = t.samba_deps[:]
312 t.samba_includes_extended = TO_LIST(t.samba_includes)[:]
313 t.ccflags = getattr(t, 'samba_cflags', '')
316 def build_direct_deps(bld, tgt_list):
317 '''build the direct_objects and direct_libs sets for each target'''
319 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
320 global_deps = bld.env.GLOBAL_DEPENDENCIES
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)
329 d = EXPAND_ALIAS(bld, d)
330 if d == t.sname: continue
332 print "Unknown dependency %s in %s" % (d, t.sname)
334 if targets[d] in [ 'EMPTY', 'DISABLED' ]:
336 if targets[d] == 'SYSLIB':
337 t.direct_syslibs.add(d)
339 t2 = bld.name_to_obj(d, bld.env)
341 print "no task %s type %s" % (d, targets[d])
342 if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
344 elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
345 t.direct_objects.add(d)
346 debug('deps: built direct dependencies')
349 def dependency_loop(loops, t, target):
350 '''add a dependency loop to the loops dictionary'''
351 if t.sname == target:
353 if not target in loops:
354 loops[target] = set()
355 if not t.sname in loops[target]:
356 loops[target].add(t.sname)
359 def indirect_libs(bld, t, chain, loops):
360 '''recursively calculate the indirect library dependencies for a target
362 An indirect library is a library that results from a dependency on
366 ret = getattr(t, 'indirect_libs', None)
371 for obj in t.direct_objects:
373 dependency_loop(loops, t, obj)
376 t2 = bld.name_to_obj(obj, bld.env)
377 r2 = indirect_libs(bld, t2, chain, loops)
379 ret = ret.union(t2.direct_libs)
382 for obj in indirect_objects(bld, t, set(), loops):
384 dependency_loop(loops, t, obj)
387 t2 = bld.name_to_obj(obj, bld.env)
388 r2 = indirect_libs(bld, t2, chain, loops)
390 ret = ret.union(t2.direct_libs)
393 t.indirect_libs = ret
398 def indirect_syslibs(bld, t, chain, loops):
399 '''recursively calculate the indirect system library dependencies for a target
401 An indirect syslib results from a subsystem dependency
404 ret = getattr(t, 'indirect_syslibs', None)
409 for obj in t.direct_objects:
411 dependency_loop(loops, t, obj)
414 t2 = bld.name_to_obj(obj, bld.env)
415 r2 = indirect_syslibs(bld, t2, chain, loops)
417 ret = ret.union(t2.direct_syslibs)
420 t.indirect_syslibs = ret
424 def indirect_objects(bld, t, chain, loops):
425 '''recursively calculate the indirect object dependencies for a target
427 indirect objects are the set of objects from expanding the
428 subsystem dependencies
431 ret = getattr(t, 'indirect_objects', None)
432 if ret is not None: return ret
435 for lib in t.direct_objects:
437 dependency_loop(loops, t, lib)
440 t2 = bld.name_to_obj(lib, bld.env)
441 r2 = indirect_objects(bld, t2, chain, loops)
443 ret = ret.union(t2.direct_objects)
446 t.indirect_objects = ret
450 def extended_objects(bld, t, chain):
451 '''recursively calculate the extended object dependencies for a target
453 extended objects are the union of:
456 - direct and indirect objects of all direct and indirect libraries
459 ret = getattr(t, 'extended_objects', None)
460 if ret is not None: return ret
463 ret = ret.union(t.direct_objects)
464 ret = ret.union(t.indirect_objects)
466 for lib in t.direct_libs:
469 t2 = bld.name_to_obj(lib, bld.env)
471 r2 = extended_objects(bld, t2, chain)
473 ret = ret.union(t2.direct_objects)
474 ret = ret.union(t2.indirect_objects)
477 t.extended_objects = ret
481 def includes_objects(bld, t, chain, inc_loops):
482 '''recursively calculate the includes object dependencies for a target
484 includes dependencies come from either library or object dependencies
486 ret = getattr(t, 'includes_objects', None)
490 ret = t.direct_objects.copy()
491 ret = ret.union(t.direct_libs)
493 for obj in t.direct_objects:
495 dependency_loop(inc_loops, t, obj)
498 t2 = bld.name_to_obj(obj, bld.env)
499 r2 = includes_objects(bld, t2, chain, inc_loops)
501 ret = ret.union(t2.direct_objects)
504 for lib in t.direct_libs:
506 dependency_loop(inc_loops, t, lib)
509 t2 = bld.name_to_obj(lib, bld.env)
510 r2 = includes_objects(bld, t2, chain, inc_loops)
512 ret = ret.union(t2.direct_objects)
515 t.includes_objects = ret
519 def break_dependency_loops(bld, tgt_list):
520 '''find and break dependency loops'''
524 # build up the list of loops
526 indirect_objects(bld, t, set(), loops)
527 indirect_libs(bld, t, set(), loops)
528 includes_objects(bld, t, set(), inc_loops)
533 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
534 objs = getattr(t, attr, set())
535 setattr(t, attr, objs.difference(loops[t.sname]))
538 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
540 # expand the loops mapping by one level
541 for loop in loops.copy():
542 for tgt in loops[loop]:
544 loops[loop] = loops[loop].union(loops[tgt])
546 # expand indirect subsystem and library loops
547 for loop in loops.copy():
548 t = bld.name_to_obj(loop, bld.env)
549 if t.samba_type in ['SUBSYSTEM']:
550 loops[loop] = loops[loop].union(t.indirect_objects)
551 loops[loop] = loops[loop].union(t.direct_objects)
552 if t.samba_type in ['LIBRARY']:
553 loops[loop] = loops[loop].union(t.indirect_libs)
554 loops[loop] = loops[loop].union(t.direct_libs)
555 if loop in loops[loop]:
556 loops[loop].remove(loop)
558 # add in the replacement dependencies
561 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
562 objs = getattr(t, attr, set())
564 diff = loops[loop].difference(objs)
568 debug('deps: Expanded target %s of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
569 objs = objs.union(diff)
570 if t.sname == 'ldb_password_hash':
571 debug('deps: setting %s %s to %s', t.sname, attr, objs)
572 setattr(t, attr, objs)
574 # now calculate the indirect syslibs, which can change from the loop expansion
576 indirect_syslibs(bld, t, set(), loops)
579 def calculate_final_deps(bld, tgt_list, loops):
580 '''calculate the final library and object dependencies'''
582 # start with the maximum possible list
583 t.final_syslibs = t.direct_syslibs.union(indirect_syslibs(bld, t, set(), loops))
584 t.final_libs = t.direct_libs.union(indirect_libs(bld, t, set(), loops))
585 t.final_objects = t.direct_objects.union(indirect_objects(bld, t, set(), loops))
588 # don't depend on ourselves
589 if t.sname in t.final_libs:
590 t.final_libs.remove(t.sname)
591 if t.sname in t.final_objects:
592 t.final_objects.remove(t.sname)
594 # find any library loops
596 if t.samba_type in ['LIBRARY', 'PYTHON']:
597 for l in t.final_libs.copy():
598 t2 = bld.name_to_obj(l, bld.env)
599 if t.sname in t2.final_libs:
600 # we could break this in either direction. If one of the libraries
601 # has a version number, and will this be distributed publicly, then
602 # we should make it the lower level library in the DAG
603 debug('deps: removing library loop %s from %s', t.sname, t2.sname)
604 dependency_loop(loops, t, t2.sname)
605 t2.final_libs.remove(t.sname)
607 for type in ['BINARY']:
609 if t.samba_type != type: continue
610 # if we will indirectly link to a target then we don't need it
611 new = t.final_objects.copy()
612 for l in t.final_libs:
613 t2 = bld.name_to_obj(l, bld.env)
614 t2_obj = extended_objects(bld, t2, set())
615 dup = new.intersection(t2_obj)
617 debug('deps: removing dups from %s of type %s: %s also in %s %s',
618 t.sname, t.samba_type, dup, t2.samba_type, l)
619 new = new.difference(dup)
621 t.final_objects = new
624 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
626 # we now need to make corrections for any library loops we broke up
627 # any target that depended on the target of the loop and doesn't
628 # depend on the source of the loop needs to get the loop source added
629 for type in ['BINARY','PYTHON','LIBRARY']:
631 if t.samba_type != type: continue
633 if loop in t.final_libs:
634 diff = loops[loop].difference(t.final_libs)
638 debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
639 t.final_libs = t.final_libs.union(diff)
641 debug('deps: removed duplicate dependencies')
644 ######################################################################
645 # this provides a way to save our dependency calculations between runs
647 savedeps_inputs = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
648 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
649 savedeps_outenv = ['INC_PATHS']
650 savedeps_caches = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS']
651 savedeps_files = ['buildtools/wafsamba/samba_deps.py']
653 def save_samba_deps(bld, tgt_list):
654 '''save the dependency calculations between builds, to make
655 further builds faster'''
656 denv = Environment.Environment()
658 denv.version = savedeps_version
659 denv.savedeps_inputs = savedeps_inputs
660 denv.savedeps_outputs = savedeps_outputs
667 for f in savedeps_files:
668 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
670 for c in savedeps_caches:
671 denv.caches[c] = LOCAL_CACHE(bld, c)
674 # save all the input attributes for each target
676 for attr in savedeps_inputs:
677 v = getattr(t, attr, None)
681 denv.input[t.sname] = tdeps
683 # save all the output attributes for each target
685 for attr in savedeps_outputs:
686 v = getattr(t, attr, None)
690 denv.output[t.sname] = tdeps
693 for attr in savedeps_outenv:
695 tdeps[attr] = t.env[attr]
697 denv.outenv[t.sname] = tdeps
699 depsfile = os.path.join(bld.bdir, "sambadeps")
703 def load_samba_deps(bld, tgt_list):
704 '''load a previous set of build dependencies if possible'''
705 depsfile = os.path.join(bld.bdir, "sambadeps")
706 denv = Environment.Environment()
708 debug('deps: checking saved dependencies')
710 if (denv.version != savedeps_version or
711 denv.savedeps_inputs != savedeps_inputs or
712 denv.savedeps_outputs != savedeps_outputs):
717 # check if critical files have changed
718 for f in savedeps_files:
719 if f not in denv.files:
721 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
724 # check if caches are the same
725 for c in savedeps_caches:
726 if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
729 # check inputs are the same
732 for attr in savedeps_inputs:
733 v = getattr(t, attr, None)
736 if t.sname in denv.input:
737 olddeps = denv.input[t.sname]
741 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
744 # put outputs in place
746 if not t.sname in denv.output: continue
747 tdeps = denv.output[t.sname]
749 setattr(t, a, tdeps[a])
751 # put output env vars in place
753 if not t.sname in denv.outenv: continue
754 tdeps = denv.outenv[t.sname]
758 debug('deps: loaded saved dependencies')
762 def check_project_rules(bld):
763 '''check the project rules - ensuring the targets are sane'''
765 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
769 # build a list of task generators we are interested in
773 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
775 t = bld.name_to_obj(tgt, bld.env)
777 print "Target %s of type %s has no task generator" % (tgt, type)
781 add_samba_attributes(bld, tgt_list)
783 if load_samba_deps(bld, tgt_list):
786 print "Checking project rules ..."
788 debug('deps: project rules checking started')
790 expand_subsystem_deps(bld)
791 build_direct_deps(bld, tgt_list)
792 break_dependency_loops(bld, tgt_list)
793 calculate_final_deps(bld, tgt_list, loops)
795 # run the various attribute generators
796 for f in [ build_dependencies, build_includes, add_init_functions ]:
797 debug('deps: project rules checking %s', f)
798 for t in tgt_list: f(t)
800 debug('deps: project rules stage1 completed')
802 #check_orpaned_targets(bld, tgt_list)
804 if not check_duplicate_sources(bld, tgt_list):
805 print "Duplicate sources present - aborting"
808 show_final_deps(bld, tgt_list)
810 debug('deps: project rules checking completed - %u targets checked',
813 save_samba_deps(bld, tgt_list)
815 print "Project rules pass"
818 def CHECK_PROJECT_RULES(bld):
819 '''enable checking of project targets for sanity'''
820 if bld.env.added_project_rules:
822 bld.env.added_project_rules = True
823 bld.add_pre_fun(check_project_rules)
824 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES