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.direct_objects)
444 ret = ret.union(t.indirect_objects)
446 for lib in t.direct_libs:
449 t2 = bld.name_to_obj(lib, bld.env)
451 r2 = extended_objects(bld, t2, chain)
453 ret = ret.union(t2.direct_objects)
454 ret = ret.union(t2.indirect_objects)
457 t.extended_objects = ret
461 def includes_objects(bld, t, chain, inc_loops):
462 '''recursively calculate the includes object dependencies for a target
464 includes dependencies come from either library or object dependencies
466 ret = getattr(t, 'includes_objects', None)
470 ret = t.direct_objects.copy()
471 ret = ret.union(t.direct_libs)
473 for obj in t.direct_objects:
475 dependency_loop(inc_loops, t, obj)
478 t2 = bld.name_to_obj(obj, bld.env)
479 r2 = includes_objects(bld, t2, chain, inc_loops)
481 ret = ret.union(t2.direct_objects)
484 for lib in t.direct_libs:
486 dependency_loop(inc_loops, t, lib)
489 t2 = bld.name_to_obj(lib, bld.env)
490 r2 = includes_objects(bld, t2, chain, inc_loops)
492 ret = ret.union(t2.direct_objects)
495 t.includes_objects = ret
499 def break_dependency_loops(bld, tgt_list):
500 '''find and break dependency loops'''
504 # build up the list of loops
506 indirect_objects(bld, t, set(), loops)
507 indirect_libs(bld, t, set(), loops)
508 includes_objects(bld, t, set(), inc_loops)
513 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
514 objs = getattr(t, attr, set())
515 setattr(t, attr, objs.difference(loops[t.sname]))
518 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
520 for loop in inc_loops:
521 debug('deps: Found include loops for target %s : %s', loop, inc_loops[loop])
523 # expand the loops mapping by one level
524 for loop in loops.copy():
525 for tgt in loops[loop]:
527 loops[loop] = loops[loop].union(loops[tgt])
529 for loop in inc_loops.copy():
530 for tgt in inc_loops[loop]:
532 inc_loops[loop] = inc_loops[loop].union(inc_loops[tgt])
535 # expand indirect subsystem and library loops
536 for loop in loops.copy():
537 t = bld.name_to_obj(loop, bld.env)
538 if t.samba_type in ['SUBSYSTEM']:
539 loops[loop] = loops[loop].union(t.indirect_objects)
540 loops[loop] = loops[loop].union(t.direct_objects)
541 if t.samba_type in ['LIBRARY','PYTHON']:
542 loops[loop] = loops[loop].union(t.indirect_libs)
543 loops[loop] = loops[loop].union(t.direct_libs)
544 if loop in loops[loop]:
545 loops[loop].remove(loop)
547 # expand indirect includes loops
548 for loop in inc_loops.copy():
549 t = bld.name_to_obj(loop, bld.env)
550 inc_loops[loop] = inc_loops[loop].union(t.includes_objects)
551 if loop in inc_loops[loop]:
552 inc_loops[loop].remove(loop)
554 # add in the replacement dependencies
557 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
558 objs = getattr(t, attr, set())
560 diff = loops[loop].difference(objs)
564 debug('deps: Expanded target %s of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
565 objs = objs.union(diff)
566 setattr(t, attr, objs)
568 for loop in inc_loops:
569 objs = getattr(t, 'includes_objects', set())
571 diff = inc_loops[loop].difference(objs)
575 debug('deps: Expanded target %s includes of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
576 objs = objs.union(diff)
577 setattr(t, 'includes_objects', objs)
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_libs = t.direct_libs.union(indirect_libs(bld, t, set(), loops))
584 t.final_objects = t.direct_objects.union(indirect_objects(bld, t, set(), loops))
587 # don't depend on ourselves
588 if t.sname in t.final_libs:
589 t.final_libs.remove(t.sname)
590 if t.sname in t.final_objects:
591 t.final_objects.remove(t.sname)
593 # find any library loops
595 if t.samba_type in ['LIBRARY', 'PYTHON']:
596 for l in t.final_libs.copy():
597 t2 = bld.name_to_obj(l, bld.env)
598 if t.sname in t2.final_libs:
599 # we could break this in either direction. If one of the libraries
600 # has a version number, and will this be distributed publicly, then
601 # we should make it the lower level library in the DAG
602 debug('deps: removing library loop %s from %s', t.sname, t2.sname)
603 dependency_loop(loops, t, t2.sname)
604 t2.final_libs.remove(t.sname)
606 for type in ['BINARY', 'PYTHON']:
608 if t.samba_type != type: continue
609 # if we will indirectly link to a target then we don't need it
610 new = t.final_objects.copy()
611 for l in t.final_libs:
612 t2 = bld.name_to_obj(l, bld.env)
613 t2_obj = extended_objects(bld, t2, set())
614 dup = new.intersection(t2_obj)
616 debug('deps: removing dups from %s of type %s: %s also in %s %s',
617 t.sname, t.samba_type, dup, t2.samba_type, l)
618 new = new.difference(dup)
620 t.final_objects = new
623 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
625 # we now need to make corrections for any library loops we broke up
626 # any target that depended on the target of the loop and doesn't
627 # depend on the source of the loop needs to get the loop source added
628 for type in ['BINARY','PYTHON','LIBRARY']:
630 if t.samba_type != type: continue
632 if loop in t.final_libs:
633 diff = loops[loop].difference(t.final_libs)
637 debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
638 t.final_libs = t.final_libs.union(diff)
640 # add in any syslib dependencies
642 if not t.samba_type in ['BINARY','PYTHON','LIBRARY']:
645 for d in t.final_objects:
646 t2 = bld.name_to_obj(d, bld.env)
647 syslibs = syslibs.union(t2.direct_syslibs)
648 # this adds the indirect syslibs as well, which may not be needed
649 # depending on the linker flags
650 for d in t.final_libs:
651 t2 = bld.name_to_obj(d, bld.env)
652 syslibs = syslibs.union(t2.direct_syslibs)
653 t.final_syslibs = syslibs
655 debug('deps: removed duplicate dependencies')
659 ######################################################################
660 # this provides a way to save our dependency calculations between runs
662 savedeps_inputs = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
663 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
664 savedeps_outenv = ['INC_PATHS']
665 savedeps_caches = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS', 'SYSLIB_DEPS']
666 savedeps_files = ['buildtools/wafsamba/samba_deps.py']
668 def save_samba_deps(bld, tgt_list):
669 '''save the dependency calculations between builds, to make
670 further builds faster'''
671 denv = Environment.Environment()
673 denv.version = savedeps_version
674 denv.savedeps_inputs = savedeps_inputs
675 denv.savedeps_outputs = savedeps_outputs
682 for f in savedeps_files:
683 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
685 for c in savedeps_caches:
686 denv.caches[c] = LOCAL_CACHE(bld, c)
689 # save all the input attributes for each target
691 for attr in savedeps_inputs:
692 v = getattr(t, attr, None)
696 denv.input[t.sname] = tdeps
698 # save all the output attributes for each target
700 for attr in savedeps_outputs:
701 v = getattr(t, attr, None)
705 denv.output[t.sname] = tdeps
708 for attr in savedeps_outenv:
710 tdeps[attr] = t.env[attr]
712 denv.outenv[t.sname] = tdeps
714 depsfile = os.path.join(bld.bdir, "sambadeps")
718 def load_samba_deps(bld, tgt_list):
719 '''load a previous set of build dependencies if possible'''
720 depsfile = os.path.join(bld.bdir, "sambadeps")
721 denv = Environment.Environment()
723 debug('deps: checking saved dependencies')
725 if (denv.version != savedeps_version or
726 denv.savedeps_inputs != savedeps_inputs or
727 denv.savedeps_outputs != savedeps_outputs):
732 # check if critical files have changed
733 for f in savedeps_files:
734 if f not in denv.files:
736 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
739 # check if caches are the same
740 for c in savedeps_caches:
741 if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
744 # check inputs are the same
747 for attr in savedeps_inputs:
748 v = getattr(t, attr, None)
751 if t.sname in denv.input:
752 olddeps = denv.input[t.sname]
756 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
759 # put outputs in place
761 if not t.sname in denv.output: continue
762 tdeps = denv.output[t.sname]
764 setattr(t, a, tdeps[a])
766 # put output env vars in place
768 if not t.sname in denv.outenv: continue
769 tdeps = denv.outenv[t.sname]
773 debug('deps: loaded saved dependencies')
777 def check_project_rules(bld):
778 '''check the project rules - ensuring the targets are sane'''
780 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
784 # build a list of task generators we are interested in
788 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
790 t = bld.name_to_obj(tgt, bld.env)
792 print "Target %s of type %s has no task generator" % (tgt, type)
796 add_samba_attributes(bld, tgt_list)
798 if load_samba_deps(bld, tgt_list):
801 print "Checking project rules ..."
803 debug('deps: project rules checking started')
805 expand_subsystem_deps(bld)
806 build_direct_deps(bld, tgt_list)
807 break_dependency_loops(bld, tgt_list)
808 calculate_final_deps(bld, tgt_list, loops)
810 # run the various attribute generators
811 for f in [ build_dependencies, build_includes, add_init_functions ]:
812 debug('deps: project rules checking %s', f)
813 for t in tgt_list: f(t)
815 debug('deps: project rules stage1 completed')
817 #check_orpaned_targets(bld, tgt_list)
819 if not check_duplicate_sources(bld, tgt_list):
820 print "Duplicate sources present - aborting"
823 show_final_deps(bld, tgt_list)
825 debug('deps: project rules checking completed - %u targets checked',
828 save_samba_deps(bld, tgt_list)
830 print "Project rules pass"
833 def CHECK_PROJECT_RULES(bld):
834 '''enable checking of project targets for sanity'''
835 if bld.env.added_project_rules:
837 bld.env.added_project_rules = True
838 bld.add_pre_fun(check_project_rules)
839 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES