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
26 def SET_SYSLIB_DEPS(conf, target, deps):
27 '''setup some implied dependencies for a SYSLIB'''
28 cache = LOCAL_CACHE(conf, 'SYSLIB_DEPS')
32 def EXPAND_ALIAS(bld, target):
33 '''expand a target name via an alias'''
34 aliases = LOCAL_CACHE(bld, 'TARGET_ALIAS')
36 return aliases[target]
38 Build.BuildContext.EXPAND_ALIAS = EXPAND_ALIAS
41 def expand_subsystem_deps(bld):
42 '''expand the reverse dependencies resulting from subsystem
43 attributes of modules'''
44 subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
45 aliases = LOCAL_CACHE(bld, 'TARGET_ALIAS')
46 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
51 bld.ASSERT(s in targets, "Subsystem target %s not declared" % s)
53 if type == 'DISABLED' or type == 'EMPTY':
56 t = bld.name_to_obj(s, bld.env)
57 bld.ASSERT(t is not None, "Subsystem target %s not found" % s)
58 for d in subsystems[s]:
59 type = targets[d['TARGET']]
60 if type != 'DISABLED' and type != 'EMPTY':
61 t.samba_deps_extended.append(d['TARGET'])
62 t2 = bld.name_to_obj(d['TARGET'], bld.env)
63 t2.samba_includes_extended.extend(t.samba_includes_extended)
64 t2.samba_deps_extended.extend(t.samba_deps_extended)
65 t.samba_deps_extended = unique_list(t.samba_deps_extended)
69 def build_dependencies(self):
70 '''This builds the dependency list for a target. It runs after all the targets are declared
72 The reason this is not just done in the SAMBA_*() rules is that we have no way of knowing
73 the full dependency list for a target until we have all of the targets declared.
76 if self.samba_type in ['LIBRARY', 'BINARY', 'PYTHON']:
77 self.uselib = list(self.final_syslibs)
78 self.uselib_local = list(self.final_libs)
79 self.add_objects = list(self.final_objects)
81 # extra link flags from pkg_config
82 libs = self.final_syslibs.copy()
84 (ccflags, ldflags) = library_flags(self, list(libs))
85 new_ldflags = getattr(self, 'ldflags', [])
86 new_ldflags.extend(ldflags)
87 self.ldflags = new_ldflags
89 debug('deps: computed dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
90 self.sname, self.uselib, self.uselib_local, self.add_objects)
92 if self.samba_type in ['SUBSYSTEM']:
93 # this is needed for the ccflags of libs that come from pkg_config
94 self.uselib = list(self.direct_syslibs)
96 if getattr(self, 'uselib', None):
99 up_list.append(l.upper())
100 self.uselib = up_list
103 def build_includes(self):
104 '''This builds the right set of includes for a target.
106 One tricky part of this is that the includes= attribute for a
107 target needs to use paths which are relative to that targets
108 declaration directory (which we can get at via t.path).
110 The way this works is the includes list gets added as
111 samba_includes in the main build task declaration. Then this
112 function runs after all of the tasks are declared, and it
113 processes the samba_includes attribute to produce a includes=
117 if getattr(self, 'samba_includes', None) is None:
122 inc_deps = includes_objects(bld, self, set(), {})
126 # maybe add local includes
127 if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
130 includes.extend(self.samba_includes_extended)
132 if 'EXTRA_INCLUDES' in bld.env:
133 includes.extend(bld.env['EXTRA_INCLUDES'])
141 t = bld.name_to_obj(d, bld.env)
142 bld.ASSERT(t is not None, "Unable to find dependency %s for %s" % (d, self.sname))
143 inclist = getattr(t, 'samba_includes_extended', [])
144 if getattr(t, 'local_include', True) == True:
148 tpath = t.samba_abspath
150 npath = tpath + '/' + inc
151 if not npath in inc_set:
152 inc_abs.append(npath)
155 mypath = self.path.abspath(bld.env)
157 relpath = os_path_relpath(inc, mypath)
158 includes.append(relpath)
160 if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
163 # now transform the includes list to be relative to the top directory
164 # which is represented by '#' in waf. This allows waf to cache the
165 # includes lists more efficiently
169 # some are already top based
170 includes_top.append(i)
172 absinc = os.path.join(self.path.abspath(), i)
173 relinc = os_path_relpath(absinc, self.bld.srcnode.abspath())
174 includes_top.append('#' + relinc)
176 self.includes = unique_list(includes_top)
177 debug('deps: includes for target %s: includes=%s',
178 self.sname, self.includes)
183 def add_init_functions(self):
184 '''This builds the right set of init functions'''
188 subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
190 # cope with the separated object lists from BINARY and LIBRARY targets
192 if sname.endswith('.objlist'):
196 if sname in subsystems:
197 modules.append(sname)
199 m = getattr(self, 'samba_modules', None)
201 modules.extend(TO_LIST(m))
203 m = getattr(self, 'samba_subsystem', None)
210 sentinal = getattr(self, 'init_function_sentinal', 'NULL')
212 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
214 cflags = getattr(self, 'samba_cflags', [])[:]
216 bld.ASSERT(m in subsystems,
217 "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
219 for d in subsystems[m]:
220 if targets[d['TARGET']] != 'DISABLED':
221 init_fn_list.append(d['INIT_FUNCTION'])
222 if init_fn_list == []:
223 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, sentinal))
225 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
226 self.ccflags = cflags
230 def check_duplicate_sources(bld, tgt_list):
231 '''see if we are compiling the same source file into multiple
232 subsystem targets for the same library or binary'''
234 debug('deps: checking for duplicate sources')
236 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
242 obj_sources = getattr(t, 'source', '')
243 tpath = os_path_relpath(t.path.abspath(bld.env), t.env['BUILD_DIRECTORY'] + '/default')
244 obj_sources = bld.SUBDIR(tpath, obj_sources)
245 t.samba_source_set = set(TO_LIST(obj_sources))
248 if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
252 for obj in t.add_objects:
253 t2 = t.bld.name_to_obj(obj, bld.env)
254 source_set = getattr(t2, 'samba_source_set', set())
255 sources.append( { 'dep':obj, 'src':source_set} )
258 if s['dep'] == s2['dep']: continue
259 common = s['src'].intersection(s2['src'])
260 if common.difference(seen):
261 print("Target %s has duplicate source files in %s and %s : %s" % (t.sname,
264 seen = seen.union(common)
269 def check_orpaned_targets(bld, tgt_list):
270 '''check if any build targets are orphaned'''
272 target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
274 debug('deps: checking for orphaned targets')
277 if getattr(t, 'samba_used', False) == True:
279 type = target_dict[t.sname]
280 if not type in ['BINARY', 'LIBRARY', 'MODULE', 'ET', 'PYTHON']:
281 if re.search('^PIDL_', t.sname) is None:
282 print "Target %s of type %s is unused by any other target" % (t.sname, type)
285 def show_final_deps(bld, tgt_list):
286 '''show the final dependencies for all targets'''
288 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
291 if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
293 debug('deps: final dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
294 t.sname, t.uselib, t.uselib_local, t.add_objects)
297 def add_samba_attributes(bld, tgt_list):
298 '''ensure a target has a the required samba attributes'''
300 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
307 t.samba_type = targets[t.sname]
308 t.samba_abspath = t.path.abspath(bld.env)
309 t.samba_deps_extended = t.samba_deps[:]
310 t.samba_includes_extended = TO_LIST(t.samba_includes)[:]
311 t.ccflags = getattr(t, 'samba_cflags', '')
314 def build_direct_deps(bld, tgt_list):
315 '''build the direct_objects and direct_libs sets for each target'''
317 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
318 syslib_deps = LOCAL_CACHE(bld, 'SYSLIB_DEPS')
319 global_deps = bld.env.GLOBAL_DEPENDENCIES
322 t.direct_objects = set()
323 t.direct_libs = set()
324 t.direct_syslibs = set()
325 deps = t.samba_deps_extended
326 deps.extend(global_deps)
328 d = EXPAND_ALIAS(bld, d)
329 if d == t.sname: continue
331 print "Unknown dependency %s in %s" % (d, t.sname)
333 if targets[d] in [ 'EMPTY', 'DISABLED' ]:
335 if targets[d] == 'SYSLIB':
336 t.direct_syslibs.add(d)
338 for implied in TO_LIST(syslib_deps[d]):
339 print("Adding implied lib %s to %s" % (implied, t.sname))
340 t.direct_libs.add(implied)
342 t2 = bld.name_to_obj(d, bld.env)
344 print "no task %s type %s" % (d, targets[d])
345 if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
347 elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
348 t.direct_objects.add(d)
349 debug('deps: built direct dependencies')
352 def dependency_loop(loops, t, target):
353 '''add a dependency loop to the loops dictionary'''
354 if t.sname == target:
356 if not target in loops:
357 loops[target] = set()
358 if not t.sname in loops[target]:
359 loops[target].add(t.sname)
362 def indirect_libs(bld, t, chain, loops):
363 '''recursively calculate the indirect library dependencies for a target
365 An indirect library is a library that results from a dependency on
369 ret = getattr(t, 'indirect_libs', None)
374 for obj in t.direct_objects:
376 dependency_loop(loops, t, obj)
379 t2 = bld.name_to_obj(obj, bld.env)
380 r2 = indirect_libs(bld, t2, chain, loops)
382 ret = ret.union(t2.direct_libs)
385 for obj in indirect_objects(bld, t, set(), loops):
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 t.indirect_libs = ret
401 def indirect_objects(bld, t, chain, loops):
402 '''recursively calculate the indirect object dependencies for a target
404 indirect objects are the set of objects from expanding the
405 subsystem dependencies
408 ret = getattr(t, 'indirect_objects', None)
409 if ret is not None: return ret
412 for lib in t.direct_objects:
414 dependency_loop(loops, t, lib)
417 t2 = bld.name_to_obj(lib, bld.env)
418 r2 = indirect_objects(bld, t2, chain, loops)
420 ret = ret.union(t2.direct_objects)
423 t.indirect_objects = ret
427 def extended_objects(bld, t, chain):
428 '''recursively calculate the extended object dependencies for a target
430 extended objects are the union of:
433 - direct and indirect objects of all direct and indirect libraries
436 ret = getattr(t, 'extended_objects', None)
437 if ret is not None: return ret
440 ret = ret.union(t.direct_objects)
441 ret = ret.union(t.indirect_objects)
443 for lib in t.direct_libs:
446 t2 = bld.name_to_obj(lib, bld.env)
448 r2 = extended_objects(bld, t2, chain)
450 ret = ret.union(t2.direct_objects)
451 ret = ret.union(t2.indirect_objects)
454 t.extended_objects = ret
458 def includes_objects(bld, t, chain, inc_loops):
459 '''recursively calculate the includes object dependencies for a target
461 includes dependencies come from either library or object dependencies
463 ret = getattr(t, 'includes_objects', None)
467 ret = t.direct_objects.copy()
468 ret = ret.union(t.direct_libs)
470 for obj in t.direct_objects:
472 dependency_loop(inc_loops, t, obj)
475 t2 = bld.name_to_obj(obj, bld.env)
476 r2 = includes_objects(bld, t2, chain, inc_loops)
478 ret = ret.union(t2.direct_objects)
481 for lib in t.direct_libs:
483 dependency_loop(inc_loops, t, lib)
486 t2 = bld.name_to_obj(lib, bld.env)
487 r2 = includes_objects(bld, t2, chain, inc_loops)
489 ret = ret.union(t2.direct_objects)
492 t.includes_objects = ret
496 def break_dependency_loops(bld, tgt_list):
497 '''find and break dependency loops'''
501 # build up the list of loops
503 indirect_objects(bld, t, set(), loops)
504 indirect_libs(bld, t, set(), loops)
505 includes_objects(bld, t, set(), inc_loops)
510 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
511 objs = getattr(t, attr, set())
512 setattr(t, attr, objs.difference(loops[t.sname]))
515 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
517 # expand the loops mapping by one level
518 for loop in loops.copy():
519 for tgt in loops[loop]:
521 loops[loop] = loops[loop].union(loops[tgt])
523 # expand indirect subsystem and library loops
524 for loop in loops.copy():
525 t = bld.name_to_obj(loop, bld.env)
526 if t.samba_type in ['SUBSYSTEM']:
527 loops[loop] = loops[loop].union(t.indirect_objects)
528 loops[loop] = loops[loop].union(t.direct_objects)
529 if t.samba_type in ['LIBRARY','PYTHON']:
530 loops[loop] = loops[loop].union(t.indirect_libs)
531 loops[loop] = loops[loop].union(t.direct_libs)
532 if loop in loops[loop]:
533 loops[loop].remove(loop)
535 # add in the replacement dependencies
538 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
539 objs = getattr(t, attr, set())
541 diff = loops[loop].difference(objs)
545 debug('deps: Expanded target %s of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
546 objs = objs.union(diff)
547 if t.sname == 'ldb_password_hash':
548 debug('deps: setting %s %s to %s', t.sname, attr, objs)
549 setattr(t, attr, objs)
551 def calculate_final_deps(bld, tgt_list, loops):
552 '''calculate the final library and object dependencies'''
554 # start with the maximum possible list
555 t.final_libs = t.direct_libs.union(indirect_libs(bld, t, set(), loops))
556 t.final_objects = t.direct_objects.union(indirect_objects(bld, t, set(), loops))
559 # don't depend on ourselves
560 if t.sname in t.final_libs:
561 t.final_libs.remove(t.sname)
562 if t.sname in t.final_objects:
563 t.final_objects.remove(t.sname)
565 # find any library loops
567 if t.samba_type in ['LIBRARY', 'PYTHON']:
568 for l in t.final_libs.copy():
569 t2 = bld.name_to_obj(l, bld.env)
570 if t.sname in t2.final_libs:
571 # we could break this in either direction. If one of the libraries
572 # has a version number, and will this be distributed publicly, then
573 # we should make it the lower level library in the DAG
574 debug('deps: removing library loop %s from %s', t.sname, t2.sname)
575 dependency_loop(loops, t, t2.sname)
576 t2.final_libs.remove(t.sname)
578 for type in ['BINARY']:
580 if t.samba_type != type: continue
581 # if we will indirectly link to a target then we don't need it
582 new = t.final_objects.copy()
583 for l in t.final_libs:
584 t2 = bld.name_to_obj(l, bld.env)
585 t2_obj = extended_objects(bld, t2, set())
586 dup = new.intersection(t2_obj)
588 debug('deps: removing dups from %s of type %s: %s also in %s %s',
589 t.sname, t.samba_type, dup, t2.samba_type, l)
590 new = new.difference(dup)
592 t.final_objects = new
595 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
597 # we now need to make corrections for any library loops we broke up
598 # any target that depended on the target of the loop and doesn't
599 # depend on the source of the loop needs to get the loop source added
600 for type in ['BINARY','PYTHON','LIBRARY']:
602 if t.samba_type != type: continue
604 if loop in t.final_libs:
605 diff = loops[loop].difference(t.final_libs)
609 debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
610 t.final_libs = t.final_libs.union(diff)
612 # add in any syslib dependencies
614 if not t.samba_type in ['BINARY','PYTHON','LIBRARY']:
617 for d in t.final_objects:
618 t2 = bld.name_to_obj(d, bld.env)
619 syslibs = syslibs.union(t2.direct_syslibs)
620 # this adds the indirect syslibs as well, which may not be needed
621 # depending on the linker flags
622 for d in t.final_libs:
623 t2 = bld.name_to_obj(d, bld.env)
624 syslibs = syslibs.union(t2.direct_syslibs)
625 t.final_syslibs = syslibs
627 debug('deps: removed duplicate dependencies')
631 ######################################################################
632 # this provides a way to save our dependency calculations between runs
634 savedeps_inputs = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
635 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
636 savedeps_outenv = ['INC_PATHS']
637 savedeps_caches = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS', 'SYSLIB_DEPS']
638 savedeps_files = ['buildtools/wafsamba/samba_deps.py']
640 def save_samba_deps(bld, tgt_list):
641 '''save the dependency calculations between builds, to make
642 further builds faster'''
643 denv = Environment.Environment()
645 denv.version = savedeps_version
646 denv.savedeps_inputs = savedeps_inputs
647 denv.savedeps_outputs = savedeps_outputs
654 for f in savedeps_files:
655 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
657 for c in savedeps_caches:
658 denv.caches[c] = LOCAL_CACHE(bld, c)
661 # save all the input attributes for each target
663 for attr in savedeps_inputs:
664 v = getattr(t, attr, None)
668 denv.input[t.sname] = tdeps
670 # save all the output attributes for each target
672 for attr in savedeps_outputs:
673 v = getattr(t, attr, None)
677 denv.output[t.sname] = tdeps
680 for attr in savedeps_outenv:
682 tdeps[attr] = t.env[attr]
684 denv.outenv[t.sname] = tdeps
686 depsfile = os.path.join(bld.bdir, "sambadeps")
690 def load_samba_deps(bld, tgt_list):
691 '''load a previous set of build dependencies if possible'''
692 depsfile = os.path.join(bld.bdir, "sambadeps")
693 denv = Environment.Environment()
695 debug('deps: checking saved dependencies')
697 if (denv.version != savedeps_version or
698 denv.savedeps_inputs != savedeps_inputs or
699 denv.savedeps_outputs != savedeps_outputs):
704 # check if critical files have changed
705 for f in savedeps_files:
706 if f not in denv.files:
708 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
711 # check if caches are the same
712 for c in savedeps_caches:
713 if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
716 # check inputs are the same
719 for attr in savedeps_inputs:
720 v = getattr(t, attr, None)
723 if t.sname in denv.input:
724 olddeps = denv.input[t.sname]
728 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
731 # put outputs in place
733 if not t.sname in denv.output: continue
734 tdeps = denv.output[t.sname]
736 setattr(t, a, tdeps[a])
738 # put output env vars in place
740 if not t.sname in denv.outenv: continue
741 tdeps = denv.outenv[t.sname]
745 debug('deps: loaded saved dependencies')
749 def check_project_rules(bld):
750 '''check the project rules - ensuring the targets are sane'''
752 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
756 # build a list of task generators we are interested in
760 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
762 t = bld.name_to_obj(tgt, bld.env)
764 print "Target %s of type %s has no task generator" % (tgt, type)
768 add_samba_attributes(bld, tgt_list)
770 if load_samba_deps(bld, tgt_list):
773 print "Checking project rules ..."
775 debug('deps: project rules checking started')
777 expand_subsystem_deps(bld)
778 build_direct_deps(bld, tgt_list)
779 break_dependency_loops(bld, tgt_list)
780 calculate_final_deps(bld, tgt_list, loops)
782 # run the various attribute generators
783 for f in [ build_dependencies, build_includes, add_init_functions ]:
784 debug('deps: project rules checking %s', f)
785 for t in tgt_list: f(t)
787 debug('deps: project rules stage1 completed')
789 #check_orpaned_targets(bld, tgt_list)
791 if not check_duplicate_sources(bld, tgt_list):
792 print "Duplicate sources present - aborting"
795 show_final_deps(bld, tgt_list)
797 debug('deps: project rules checking completed - %u targets checked',
800 save_samba_deps(bld, tgt_list)
802 print "Project rules pass"
805 def CHECK_PROJECT_RULES(bld):
806 '''enable checking of project targets for sanity'''
807 if bld.env.added_project_rules:
809 bld.env.added_project_rules = True
810 bld.add_pre_fun(check_project_rules)
811 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES