1 # Samba automatic dependency handling and project rules
3 import Build, os, re, Environment
4 from samba_utils import *
5 from samba_autoconf import *
6 from samba_bundled import BUILTIN_LIBRARY
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)
16 def TARGET_ALIAS(bld, target, alias):
17 '''define an alias for a target name'''
18 cache = LOCAL_CACHE(bld, 'TARGET_ALIAS')
20 print("Target alias %s already set to %s : newalias %s" % (alias, cache[alias], target))
23 Build.BuildContext.TARGET_ALIAS = TARGET_ALIAS
27 def SET_SYSLIB_DEPS(conf, target, deps):
28 '''setup some implied dependencies for a SYSLIB'''
29 cache = LOCAL_CACHE(conf, 'SYSLIB_DEPS')
33 def EXPAND_ALIAS(bld, target):
34 '''expand a target name via an alias'''
35 aliases = LOCAL_CACHE(bld, 'TARGET_ALIAS')
37 return aliases[target]
39 Build.BuildContext.EXPAND_ALIAS = EXPAND_ALIAS
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')
52 bld.ASSERT(s in targets, "Subsystem target %s not declared" % s)
54 if type == 'DISABLED' or type == 'EMPTY':
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)
70 def build_dependencies(self):
71 '''This builds the dependency list for a target. It runs after all the targets are declared
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.
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)
82 # extra link flags from pkg_config
83 libs = self.final_syslibs.copy()
85 (ccflags, ldflags) = library_flags(self, list(libs))
86 new_ldflags = getattr(self, 'ldflags', [])
87 new_ldflags.extend(ldflags)
88 self.ldflags = new_ldflags
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)
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)
97 if getattr(self, 'uselib', None):
100 up_list.append(l.upper())
101 self.uselib = up_list
104 def build_includes(self):
105 '''This builds the right set of includes for a target.
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).
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=
118 if getattr(self, 'samba_includes', None) is None:
123 inc_deps = includes_objects(bld, self, set(), {})
127 # maybe add local includes
128 if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
131 includes.extend(self.samba_includes_extended)
133 if 'EXTRA_INCLUDES' in bld.env:
134 includes.extend(bld.env['EXTRA_INCLUDES'])
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:
149 tpath = t.samba_abspath
151 npath = tpath + '/' + inc
152 if not npath in inc_set:
153 inc_abs.append(npath)
156 mypath = self.path.abspath(bld.env)
158 relpath = os_path_relpath(inc, mypath)
159 includes.append(relpath)
161 if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
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
170 # some are already top based
171 includes_top.append(i)
173 absinc = os.path.join(self.path.abspath(), i)
174 relinc = os_path_relpath(absinc, self.bld.srcnode.abspath())
175 includes_top.append('#' + relinc)
177 self.includes = unique_list(includes_top)
178 debug('deps: includes for target %s: includes=%s',
179 self.sname, self.includes)
184 def add_init_functions(self):
185 '''This builds the right set of init functions'''
189 subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
191 # cope with the separated object lists from BINARY and LIBRARY targets
193 if sname.endswith('.objlist'):
197 if sname in subsystems:
198 modules.append(sname)
200 m = getattr(self, 'samba_modules', None)
202 modules.extend(TO_LIST(m))
204 m = getattr(self, 'samba_subsystem', None)
211 sentinal = getattr(self, 'init_function_sentinal', 'NULL')
213 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
215 cflags = getattr(self, 'samba_cflags', [])[:]
217 bld.ASSERT(m in subsystems,
218 "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
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))
226 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
227 self.ccflags = cflags
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'''
235 debug('deps: checking for duplicate sources')
237 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
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))
249 if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
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} )
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,
265 seen = seen.union(common)
270 def check_orpaned_targets(bld, tgt_list):
271 '''check if any build targets are orphaned'''
273 target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
275 debug('deps: checking for orphaned targets')
278 if getattr(t, 'samba_used', False) == True:
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)
286 def show_final_deps(bld, tgt_list):
287 '''show the final dependencies for all targets'''
289 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
292 if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
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)
298 def add_samba_attributes(bld, tgt_list):
299 '''ensure a target has a the required samba attributes'''
301 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
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', '')
315 def build_direct_deps(bld, tgt_list):
316 '''build the direct_objects and direct_libs sets for each target'''
318 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
319 syslib_deps = LOCAL_CACHE(bld, 'SYSLIB_DEPS')
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 for implied in TO_LIST(syslib_deps[d]):
340 if BUILTIN_LIBRARY(bld, implied):
341 t.direct_objects.add(implied)
343 t.direct_libs.add(implied)
345 t2 = bld.name_to_obj(d, bld.env)
347 print "no task %s type %s" % (d, targets[d])
348 if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
350 elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
351 t.direct_objects.add(d)
352 debug('deps: built direct dependencies')
355 def dependency_loop(loops, t, target):
356 '''add a dependency loop to the loops dictionary'''
357 if t.sname == target:
359 if not target in loops:
360 loops[target] = set()
361 if not t.sname in loops[target]:
362 loops[target].add(t.sname)
365 def indirect_libs(bld, t, chain, loops):
366 '''recursively calculate the indirect library dependencies for a target
368 An indirect library is a library that results from a dependency on
372 ret = getattr(t, 'indirect_libs', None)
377 for obj in t.direct_objects:
379 dependency_loop(loops, t, obj)
382 t2 = bld.name_to_obj(obj, bld.env)
383 r2 = indirect_libs(bld, t2, chain, loops)
385 ret = ret.union(t2.direct_libs)
388 for obj in indirect_objects(bld, t, set(), loops):
390 dependency_loop(loops, t, obj)
393 t2 = bld.name_to_obj(obj, bld.env)
394 r2 = indirect_libs(bld, t2, chain, loops)
396 ret = ret.union(t2.direct_libs)
399 t.indirect_libs = ret
404 def indirect_objects(bld, t, chain, loops):
405 '''recursively calculate the indirect object dependencies for a target
407 indirect objects are the set of objects from expanding the
408 subsystem dependencies
411 ret = getattr(t, 'indirect_objects', None)
412 if ret is not None: return ret
415 for lib in t.direct_objects:
417 dependency_loop(loops, t, lib)
420 t2 = bld.name_to_obj(lib, bld.env)
421 r2 = indirect_objects(bld, t2, chain, loops)
423 ret = ret.union(t2.direct_objects)
426 t.indirect_objects = ret
430 def extended_objects(bld, t, chain):
431 '''recursively calculate the extended object dependencies for a target
433 extended objects are the union of:
436 - direct and indirect objects of all direct and indirect libraries
439 ret = getattr(t, 'extended_objects', None)
440 if ret is not None: return ret
443 ret = ret.union(t.final_objects)
445 for lib in t.final_libs:
448 t2 = bld.name_to_obj(lib, bld.env)
450 r2 = extended_objects(bld, t2, chain)
452 ret = ret.union(t2.final_objects)
455 t.extended_objects = ret
459 def includes_objects(bld, t, chain, inc_loops):
460 '''recursively calculate the includes object dependencies for a target
462 includes dependencies come from either library or object dependencies
464 ret = getattr(t, 'includes_objects', None)
468 ret = t.direct_objects.copy()
469 ret = ret.union(t.direct_libs)
471 for obj in t.direct_objects:
473 dependency_loop(inc_loops, t, obj)
476 t2 = bld.name_to_obj(obj, bld.env)
477 r2 = includes_objects(bld, t2, chain, inc_loops)
479 ret = ret.union(t2.direct_objects)
482 for lib in t.direct_libs:
484 dependency_loop(inc_loops, t, lib)
487 t2 = bld.name_to_obj(lib, bld.env)
488 r2 = includes_objects(bld, t2, chain, inc_loops)
490 ret = ret.union(t2.direct_objects)
493 t.includes_objects = ret
497 def break_dependency_loops(bld, tgt_list):
498 '''find and break dependency loops'''
502 # build up the list of loops
504 indirect_objects(bld, t, set(), loops)
505 indirect_libs(bld, t, set(), loops)
506 includes_objects(bld, t, set(), inc_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]))
516 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
518 for loop in inc_loops:
519 debug('deps: Found include loops for target %s : %s', loop, inc_loops[loop])
521 # expand the loops mapping by one level
522 for loop in loops.copy():
523 for tgt in loops[loop]:
525 loops[loop] = loops[loop].union(loops[tgt])
527 for loop in inc_loops.copy():
528 for tgt in inc_loops[loop]:
530 inc_loops[loop] = inc_loops[loop].union(inc_loops[tgt])
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)
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)
552 # add in the replacement dependencies
555 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
556 objs = getattr(t, attr, set())
558 diff = loops[loop].difference(objs)
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)
566 for loop in inc_loops:
567 objs = getattr(t, 'includes_objects', set())
569 diff = inc_loops[loop].difference(objs)
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)
578 def reduce_objects(bld, tgt_list):
579 '''reduce objects by looking for indirect object dependencies'''
583 t.extended_objects = None
585 for type in ['BINARY', 'PYTHON', 'LIBRARY']:
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)
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)
601 rely_on[l] = rely_on[l].union(dup)
602 t.final_objects = new
604 # add back in any objects that were relied upon by the reduction rules
606 t = bld.name_to_obj(r, bld.env)
607 t.final_objects = t.final_objects.union(rely_on[r])
610 def calculate_final_deps(bld, tgt_list, loops):
611 '''calculate the final library and object dependencies'''
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))
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)
625 # find any library loops
627 if t.samba_type in ['LIBRARY', 'PYTHON']:
628 for l in t.final_libs.copy():
629 t2 = bld.name_to_obj(l, bld.env)
630 if t.sname in t2.final_libs:
631 # we could break this in either direction. If one of the libraries
632 # has a version number, and will this be distributed publicly, then
633 # we should make it the lower level library in the DAG
634 debug('deps: removing library loop %s from %s', t.sname, t2.sname)
635 dependency_loop(loops, t, t2.sname)
636 t2.final_libs.remove(t.sname)
640 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
642 # we now need to make corrections for any library loops we broke up
643 # any target that depended on the target of the loop and doesn't
644 # depend on the source of the loop needs to get the loop source added
645 for type in ['BINARY','PYTHON','LIBRARY','BINARY']:
647 if t.samba_type != type: continue
649 if loop in t.final_libs:
650 diff = loops[loop].difference(t.final_libs)
654 debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
655 t.final_libs = t.final_libs.union(diff)
657 # remove objects that are also available in linked libs
658 reduce_objects(bld, tgt_list)
659 reduce_objects(bld, tgt_list)
661 # add in any syslib dependencies
663 if not t.samba_type in ['BINARY','PYTHON','LIBRARY']:
666 for d in t.final_objects:
667 t2 = bld.name_to_obj(d, bld.env)
668 syslibs = syslibs.union(t2.direct_syslibs)
669 # this adds the indirect syslibs as well, which may not be needed
670 # depending on the linker flags
671 for d in t.final_libs:
672 t2 = bld.name_to_obj(d, bld.env)
673 syslibs = syslibs.union(t2.direct_syslibs)
674 t.final_syslibs = syslibs
676 debug('deps: removed duplicate dependencies')
679 ######################################################################
680 # this provides a way to save our dependency calculations between runs
682 savedeps_inputs = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
683 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
684 savedeps_outenv = ['INC_PATHS']
685 savedeps_caches = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS', 'SYSLIB_DEPS']
686 savedeps_files = ['buildtools/wafsamba/samba_deps.py']
688 def save_samba_deps(bld, tgt_list):
689 '''save the dependency calculations between builds, to make
690 further builds faster'''
691 denv = Environment.Environment()
693 denv.version = savedeps_version
694 denv.savedeps_inputs = savedeps_inputs
695 denv.savedeps_outputs = savedeps_outputs
702 for f in savedeps_files:
703 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
705 for c in savedeps_caches:
706 denv.caches[c] = LOCAL_CACHE(bld, c)
709 # save all the input attributes for each target
711 for attr in savedeps_inputs:
712 v = getattr(t, attr, None)
716 denv.input[t.sname] = tdeps
718 # save all the output attributes for each target
720 for attr in savedeps_outputs:
721 v = getattr(t, attr, None)
725 denv.output[t.sname] = tdeps
728 for attr in savedeps_outenv:
730 tdeps[attr] = t.env[attr]
732 denv.outenv[t.sname] = tdeps
734 depsfile = os.path.join(bld.bdir, "sambadeps")
739 def load_samba_deps(bld, tgt_list):
740 '''load a previous set of build dependencies if possible'''
741 depsfile = os.path.join(bld.bdir, "sambadeps")
742 denv = Environment.Environment()
744 debug('deps: checking saved dependencies')
746 if (denv.version != savedeps_version or
747 denv.savedeps_inputs != savedeps_inputs or
748 denv.savedeps_outputs != savedeps_outputs):
753 # check if critical files have changed
754 for f in savedeps_files:
755 if f not in denv.files:
757 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
760 # check if caches are the same
761 for c in savedeps_caches:
762 if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
765 # check inputs are the same
768 for attr in savedeps_inputs:
769 v = getattr(t, attr, None)
772 if t.sname in denv.input:
773 olddeps = denv.input[t.sname]
777 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
780 # put outputs in place
782 if not t.sname in denv.output: continue
783 tdeps = denv.output[t.sname]
785 setattr(t, a, tdeps[a])
787 # put output env vars in place
789 if not t.sname in denv.outenv: continue
790 tdeps = denv.outenv[t.sname]
794 debug('deps: loaded saved dependencies')
799 def check_project_rules(bld):
800 '''check the project rules - ensuring the targets are sane'''
802 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
806 # build a list of task generators we are interested in
810 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
812 t = bld.name_to_obj(tgt, bld.env)
814 print "Target %s of type %s has no task generator" % (tgt, type)
818 add_samba_attributes(bld, tgt_list)
820 if load_samba_deps(bld, tgt_list):
823 print "Checking project rules ..."
825 debug('deps: project rules checking started')
827 expand_subsystem_deps(bld)
828 build_direct_deps(bld, tgt_list)
829 break_dependency_loops(bld, tgt_list)
830 calculate_final_deps(bld, tgt_list, loops)
832 # run the various attribute generators
833 for f in [ build_dependencies, build_includes, add_init_functions ]:
834 debug('deps: project rules checking %s', f)
835 for t in tgt_list: f(t)
837 debug('deps: project rules stage1 completed')
839 #check_orpaned_targets(bld, tgt_list)
841 if not check_duplicate_sources(bld, tgt_list):
842 print "Duplicate sources present - aborting"
845 show_final_deps(bld, tgt_list)
847 debug('deps: project rules checking completed - %u targets checked',
850 save_samba_deps(bld, tgt_list)
852 print "Project rules pass"
855 def CHECK_PROJECT_RULES(bld):
856 '''enable checking of project targets for sanity'''
857 if bld.env.added_project_rules:
859 bld.env.added_project_rules = True
860 bld.add_pre_fun(check_project_rules)
861 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES