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 need to link against:
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
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)
83 # extra link flags from pkg_config
84 libs = self.final_syslibs.copy()
86 (ccflags, ldflags) = library_flags(self, list(libs))
87 new_ldflags = getattr(self, 'ldflags', [])
88 new_ldflags.extend(ldflags)
89 self.ldflags = new_ldflags
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)
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)
98 if getattr(self, 'uselib', None):
100 for l in self.uselib:
101 up_list.append(l.upper())
102 self.uselib = up_list
105 def build_includes(self):
106 '''This builds the right set of includes for a target.
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).
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=
119 if getattr(self, 'samba_includes', None) is None:
124 inc_deps = includes_objects(bld, self, set(), {})
128 # maybe add local includes
129 if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
132 includes.extend(self.samba_includes_extended)
134 if 'EXTRA_INCLUDES' in bld.env:
135 includes.extend(bld.env['EXTRA_INCLUDES'])
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:
150 tpath = t.samba_abspath
152 npath = tpath + '/' + inc
153 if not npath in inc_set:
154 inc_abs.append(npath)
157 mypath = self.path.abspath(bld.env)
159 relpath = os_path_relpath(inc, mypath)
160 includes.append(relpath)
162 if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
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
171 # some are already top based
172 includes_top.append(i)
174 absinc = os.path.join(self.path.abspath(), i)
175 relinc = os_path_relpath(absinc, self.bld.srcnode.abspath())
176 includes_top.append('#' + relinc)
178 self.includes = unique_list(includes_top)
179 debug('deps: includes for target %s: includes=%s',
180 self.sname, self.includes)
185 def add_init_functions(self):
186 '''This builds the right set of init functions'''
190 subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
192 # cope with the separated object lists from BINARY and LIBRARY targets
194 if sname.endswith('.objlist'):
198 if sname in subsystems:
199 modules.append(sname)
201 m = getattr(self, 'samba_modules', None)
203 modules.extend(TO_LIST(m))
205 m = getattr(self, 'samba_subsystem', None)
212 sentinal = getattr(self, 'init_function_sentinal', 'NULL')
214 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
216 cflags = getattr(self, 'samba_cflags', [])[:]
218 bld.ASSERT(m in subsystems,
219 "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
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))
227 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
228 self.ccflags = cflags
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'''
236 debug('deps: checking for duplicate sources')
238 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
244 # this was useful for finding problems with the autogenerated rules
247 sources = TO_LIST(getattr(t, 'source', ''))
249 bname = os.path.basename(s)
250 if bname in base_list:
251 print "Suspicious duplicate name %s in %s" % (bname, t.sname)
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))
264 if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
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} )
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,
280 seen = seen.union(common)
285 def check_orpaned_targets(bld, tgt_list):
286 '''check if any build targets are orphaned'''
288 target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
290 debug('deps: checking for orphaned targets')
293 if getattr(t, 'samba_used', False) == True:
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)
301 def show_final_deps(bld, tgt_list):
302 '''show the final dependencies for all targets'''
304 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
307 if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
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)
313 def add_samba_attributes(bld, tgt_list):
314 '''ensure a target has a the required samba attributes'''
316 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
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', '')
330 def build_direct_deps(bld, tgt_list):
331 '''build the direct_objects and direct_libs sets for each target'''
333 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
334 global_deps = bld.env.GLOBAL_DEPENDENCIES
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)
343 d = EXPAND_ALIAS(bld, d)
344 if d == t.sname: continue
346 print "Unknown dependency %s in %s" % (d, t.sname)
348 if targets[d] in [ 'EMPTY', 'DISABLED' ]:
350 if targets[d] == 'SYSLIB':
351 t.direct_syslibs.add(d)
353 t2 = bld.name_to_obj(d, bld.env)
355 print "no task %s type %s" % (d, targets[d])
356 if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
358 elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
359 t.direct_objects.add(d)
360 debug('deps: built direct dependencies')
363 def dependency_loop(loops, t, target):
364 '''add a dependency loop to the loops dictionary'''
365 if t.sname == target:
367 if not target in loops:
368 loops[target] = set()
369 if not t.sname in loops[target]:
370 loops[target].add(t.sname)
373 def indirect_libs(bld, t, chain, loops):
374 '''recursively calculate the indirect library dependencies for a target
376 An indirect library is a library that results from a dependency on
380 ret = getattr(t, 'indirect_libs', None)
385 for obj in t.direct_objects:
387 dependency_loop(loops, t, obj)
390 t2 = bld.name_to_obj(obj, bld.env)
391 r2 = indirect_libs(bld, t2, chain, loops)
393 ret = ret.union(t2.direct_libs)
396 for obj in indirect_objects(bld, t, set(), loops):
398 dependency_loop(loops, t, obj)
401 t2 = bld.name_to_obj(obj, bld.env)
402 r2 = indirect_libs(bld, t2, chain, loops)
404 ret = ret.union(t2.direct_libs)
407 t.indirect_libs = ret
412 def indirect_objects(bld, t, chain, loops):
413 '''recursively calculate the indirect object dependencies for a target
415 indirect objects are the set of objects from expanding the
416 subsystem dependencies
419 ret = getattr(t, 'indirect_objects', None)
420 if ret is not None: return ret
423 for lib in t.direct_objects:
425 dependency_loop(loops, t, lib)
428 t2 = bld.name_to_obj(lib, bld.env)
429 r2 = indirect_objects(bld, t2, chain, loops)
431 ret = ret.union(t2.direct_objects)
434 t.indirect_objects = ret
438 def extended_objects(bld, t, chain):
439 '''recursively calculate the extended object dependencies for a target
441 extended objects are the union of:
444 - direct and indirect objects of all direct and indirect libraries
447 ret = getattr(t, 'extended_objects', None)
448 if ret is not None: return ret
451 ret = ret.union(t.direct_objects)
452 ret = ret.union(t.indirect_objects)
454 for lib in t.direct_libs:
457 t2 = bld.name_to_obj(lib, bld.env)
459 r2 = extended_objects(bld, t2, chain)
461 ret = ret.union(t2.direct_objects)
462 ret = ret.union(t2.indirect_objects)
465 t.extended_objects = ret
469 def includes_objects(bld, t, chain, inc_loops):
470 '''recursively calculate the includes object dependencies for a target
472 includes dependencies come from either library or object dependencies
474 ret = getattr(t, 'includes_objects', None)
478 ret = t.direct_objects.copy()
479 ret = ret.union(t.direct_libs)
481 for obj in t.direct_objects:
483 dependency_loop(inc_loops, t, obj)
486 t2 = bld.name_to_obj(obj, bld.env)
487 r2 = includes_objects(bld, t2, chain, inc_loops)
489 ret = ret.union(t2.direct_objects)
492 for lib in t.direct_libs:
494 dependency_loop(inc_loops, t, lib)
497 t2 = bld.name_to_obj(lib, bld.env)
498 r2 = includes_objects(bld, t2, chain, inc_loops)
500 ret = ret.union(t2.direct_objects)
503 t.includes_objects = ret
507 def break_dependency_loops(bld, tgt_list):
508 '''find and break dependency loops'''
512 # build up the list of loops
514 indirect_objects(bld, t, set(), loops)
515 indirect_libs(bld, t, set(), loops)
516 includes_objects(bld, t, set(), inc_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]))
526 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
528 # expand the loops mapping by one level
529 for loop in loops.copy():
530 for tgt in loops[loop]:
532 loops[loop] = loops[loop].union(loops[tgt])
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)
546 # add in the replacement dependencies
549 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
550 objs = getattr(t, attr, set())
552 diff = loops[loop].difference(objs)
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)
562 def calculate_final_deps(bld, tgt_list, loops):
563 '''calculate the final library and object dependencies'''
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))
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)
576 # find any library loops
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)
589 for type in ['BINARY']:
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)
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)
603 t.final_objects = new
606 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
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']:
613 if t.samba_type != type: continue
615 if loop in t.final_libs:
616 diff = loops[loop].difference(t.final_libs)
620 debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
621 t.final_libs = t.final_libs.union(diff)
623 # add in any syslib dependencies
625 if not t.samba_type in ['BINARY','PYTHON','LIBRARY']:
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
638 debug('deps: removed duplicate dependencies')
641 ######################################################################
642 # this provides a way to save our dependency calculations between runs
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']
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()
655 denv.version = savedeps_version
656 denv.savedeps_inputs = savedeps_inputs
657 denv.savedeps_outputs = savedeps_outputs
664 for f in savedeps_files:
665 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
667 for c in savedeps_caches:
668 denv.caches[c] = LOCAL_CACHE(bld, c)
671 # save all the input attributes for each target
673 for attr in savedeps_inputs:
674 v = getattr(t, attr, None)
678 denv.input[t.sname] = tdeps
680 # save all the output attributes for each target
682 for attr in savedeps_outputs:
683 v = getattr(t, attr, None)
687 denv.output[t.sname] = tdeps
690 for attr in savedeps_outenv:
692 tdeps[attr] = t.env[attr]
694 denv.outenv[t.sname] = tdeps
696 depsfile = os.path.join(bld.bdir, "sambadeps")
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()
705 debug('deps: checking saved dependencies')
707 if (denv.version != savedeps_version or
708 denv.savedeps_inputs != savedeps_inputs or
709 denv.savedeps_outputs != savedeps_outputs):
714 # check if critical files have changed
715 for f in savedeps_files:
716 if f not in denv.files:
718 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
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):
726 # check inputs are the same
729 for attr in savedeps_inputs:
730 v = getattr(t, attr, None)
733 if t.sname in denv.input:
734 olddeps = denv.input[t.sname]
738 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
741 # put outputs in place
743 if not t.sname in denv.output: continue
744 tdeps = denv.output[t.sname]
746 setattr(t, a, tdeps[a])
748 # put output env vars in place
750 if not t.sname in denv.outenv: continue
751 tdeps = denv.outenv[t.sname]
755 debug('deps: loaded saved dependencies')
759 def check_project_rules(bld):
760 '''check the project rules - ensuring the targets are sane'''
762 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
766 # build a list of task generators we are interested in
770 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
772 t = bld.name_to_obj(tgt, bld.env)
774 print "Target %s of type %s has no task generator" % (tgt, type)
778 add_samba_attributes(bld, tgt_list)
780 if load_samba_deps(bld, tgt_list):
783 print "Checking project rules ..."
785 debug('deps: project rules checking started')
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)
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)
797 debug('deps: project rules stage1 completed')
799 #check_orpaned_targets(bld, tgt_list)
801 if not check_duplicate_sources(bld, tgt_list):
802 print "Duplicate sources present - aborting"
805 show_final_deps(bld, tgt_list)
807 debug('deps: project rules checking completed - %u targets checked',
810 save_samba_deps(bld, tgt_list)
812 print "Project rules pass"
815 def CHECK_PROJECT_RULES(bld):
816 '''enable checking of project targets for sanity'''
817 if bld.env.added_project_rules:
819 bld.env.added_project_rules = True
820 bld.add_pre_fun(check_project_rules)
821 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES