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
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 if BUILTIN_LIBRARY(bld, implied):
340 t.direct_objects.add(implied)
342 t.direct_libs.add(implied)
344 t2 = bld.name_to_obj(d, bld.env)
346 print("no task %s type %s" % (d, targets[d]))
347 if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
349 elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
350 t.direct_objects.add(d)
351 debug('deps: built direct dependencies')
354 def dependency_loop(loops, t, target):
355 '''add a dependency loop to the loops dictionary'''
356 if t.sname == target:
358 if not target in loops:
359 loops[target] = set()
360 if not t.sname in loops[target]:
361 loops[target].add(t.sname)
364 def indirect_libs(bld, t, chain, loops):
365 '''recursively calculate the indirect library dependencies for a target
367 An indirect library is a library that results from a dependency on
371 ret = getattr(t, 'indirect_libs', None)
376 for obj in t.direct_objects:
378 dependency_loop(loops, t, obj)
381 t2 = bld.name_to_obj(obj, bld.env)
382 r2 = indirect_libs(bld, t2, chain, loops)
384 ret = ret.union(t2.direct_libs)
387 for obj in indirect_objects(bld, t, set(), loops):
389 dependency_loop(loops, t, obj)
392 t2 = bld.name_to_obj(obj, bld.env)
393 r2 = indirect_libs(bld, t2, chain, loops)
395 ret = ret.union(t2.direct_libs)
398 t.indirect_libs = ret
403 def indirect_objects(bld, t, chain, loops):
404 '''recursively calculate the indirect object dependencies for a target
406 indirect objects are the set of objects from expanding the
407 subsystem dependencies
410 ret = getattr(t, 'indirect_objects', None)
411 if ret is not None: return ret
414 for lib in t.direct_objects:
416 dependency_loop(loops, t, lib)
419 t2 = bld.name_to_obj(lib, bld.env)
420 r2 = indirect_objects(bld, t2, chain, loops)
422 ret = ret.union(t2.direct_objects)
425 t.indirect_objects = ret
429 def extended_objects(bld, t, chain):
430 '''recursively calculate the extended object dependencies for a target
432 extended objects are the union of:
435 - direct and indirect objects of all direct and indirect libraries
438 ret = getattr(t, 'extended_objects', None)
439 if ret is not None: return ret
442 ret = ret.union(t.final_objects)
444 for lib in t.final_libs:
447 t2 = bld.name_to_obj(lib, bld.env)
449 r2 = extended_objects(bld, t2, chain)
451 ret = ret.union(t2.final_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 for loop in inc_loops:
518 debug('deps: Found include loops for target %s : %s', loop, inc_loops[loop])
520 # expand the loops mapping by one level
521 for loop in loops.copy():
522 for tgt in loops[loop]:
524 loops[loop] = loops[loop].union(loops[tgt])
526 for loop in inc_loops.copy():
527 for tgt in inc_loops[loop]:
529 inc_loops[loop] = inc_loops[loop].union(inc_loops[tgt])
532 # expand indirect subsystem and library loops
533 for loop in loops.copy():
534 t = bld.name_to_obj(loop, bld.env)
535 if t.samba_type in ['SUBSYSTEM']:
536 loops[loop] = loops[loop].union(t.indirect_objects)
537 loops[loop] = loops[loop].union(t.direct_objects)
538 if t.samba_type in ['LIBRARY','PYTHON']:
539 loops[loop] = loops[loop].union(t.indirect_libs)
540 loops[loop] = loops[loop].union(t.direct_libs)
541 if loop in loops[loop]:
542 loops[loop].remove(loop)
544 # expand indirect includes loops
545 for loop in inc_loops.copy():
546 t = bld.name_to_obj(loop, bld.env)
547 inc_loops[loop] = inc_loops[loop].union(t.includes_objects)
548 if loop in inc_loops[loop]:
549 inc_loops[loop].remove(loop)
551 # add in the replacement dependencies
554 for attr in ['direct_objects', 'indirect_objects', 'direct_libs', 'indirect_libs']:
555 objs = getattr(t, attr, set())
557 diff = loops[loop].difference(objs)
561 debug('deps: Expanded target %s of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
562 objs = objs.union(diff)
563 setattr(t, attr, objs)
565 for loop in inc_loops:
566 objs = getattr(t, 'includes_objects', set())
568 diff = inc_loops[loop].difference(objs)
572 debug('deps: Expanded target %s includes of type %s from loop %s by %s', t.sname, t.samba_type, loop, diff)
573 objs = objs.union(diff)
574 setattr(t, 'includes_objects', objs)
577 def reduce_objects(bld, tgt_list):
578 '''reduce objects by looking for indirect object dependencies'''
582 t.extended_objects = None
586 for type in ['BINARY', 'PYTHON', 'LIBRARY']:
588 if t.samba_type != type: continue
589 # if we will indirectly link to a target then we don't need it
590 new = t.final_objects.copy()
591 for l in t.final_libs:
592 t2 = bld.name_to_obj(l, bld.env)
593 t2_obj = extended_objects(bld, t2, set())
594 dup = new.intersection(t2_obj)
596 debug('deps: removing dups from %s of type %s: %s also in %s %s',
597 t.sname, t.samba_type, dup, t2.samba_type, l)
598 new = new.difference(dup)
602 rely_on[l] = rely_on[l].union(dup)
603 t.final_objects = new
608 # add back in any objects that were relied upon by the reduction rules
610 t = bld.name_to_obj(r, bld.env)
611 t.final_objects = t.final_objects.union(rely_on[r])
616 def calculate_final_deps(bld, tgt_list, loops):
617 '''calculate the final library and object dependencies'''
619 # start with the maximum possible list
620 t.final_libs = t.direct_libs.union(indirect_libs(bld, t, set(), loops))
621 t.final_objects = t.direct_objects.union(indirect_objects(bld, t, set(), loops))
624 # don't depend on ourselves
625 if t.sname in t.final_libs:
626 t.final_libs.remove(t.sname)
627 if t.sname in t.final_objects:
628 t.final_objects.remove(t.sname)
631 # find any library loops
633 if t.samba_type in ['LIBRARY', 'PYTHON']:
634 for l in t.final_libs.copy():
635 t2 = bld.name_to_obj(l, bld.env)
636 if t.sname in t2.final_libs:
637 # we could break this in either direction. If one of the libraries
638 # has a version number, and will this be distributed publicly, then
639 # we should make it the lower level library in the DAG
640 debug('deps: removing library loop %s from %s', t.sname, t2.sname)
641 dependency_loop(loops, t, t2.sname)
642 t2.final_libs.remove(t.sname)
646 debug('deps: Found dependency loops for target %s : %s', loop, loops[loop])
648 # we now need to make corrections for any library loops we broke up
649 # any target that depended on the target of the loop and doesn't
650 # depend on the source of the loop needs to get the loop source added
651 for type in ['BINARY','PYTHON','LIBRARY','BINARY']:
653 if t.samba_type != type: continue
655 if loop in t.final_libs:
656 diff = loops[loop].difference(t.final_libs)
660 debug('deps: Expanded target %s by loop %s libraries %s', t.sname, loop, diff)
661 t.final_libs = t.final_libs.union(diff)
663 # remove objects that are also available in linked libs
665 while reduce_objects(bld, tgt_list):
668 print("WARNING: Unable to remove all inter-target object duplicates")
670 debug('deps: Object reduction took %u iterations', count)
672 # add in any syslib dependencies
674 if not t.samba_type in ['BINARY','PYTHON','LIBRARY']:
677 for d in t.final_objects:
678 t2 = bld.name_to_obj(d, bld.env)
679 syslibs = syslibs.union(t2.direct_syslibs)
680 # this adds the indirect syslibs as well, which may not be needed
681 # depending on the linker flags
682 for d in t.final_libs:
683 t2 = bld.name_to_obj(d, bld.env)
684 syslibs = syslibs.union(t2.direct_syslibs)
685 t.final_syslibs = syslibs
687 debug('deps: removed duplicate dependencies')
690 ######################################################################
691 # this provides a way to save our dependency calculations between runs
693 savedeps_inputs = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
694 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
695 savedeps_outenv = ['INC_PATHS']
696 savedeps_caches = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS', 'SYSLIB_DEPS']
697 savedeps_files = ['buildtools/wafsamba/samba_deps.py']
699 def save_samba_deps(bld, tgt_list):
700 '''save the dependency calculations between builds, to make
701 further builds faster'''
702 denv = Environment.Environment()
704 denv.version = savedeps_version
705 denv.savedeps_inputs = savedeps_inputs
706 denv.savedeps_outputs = savedeps_outputs
713 for f in savedeps_files:
714 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
716 for c in savedeps_caches:
717 denv.caches[c] = LOCAL_CACHE(bld, c)
720 # save all the input attributes for each target
722 for attr in savedeps_inputs:
723 v = getattr(t, attr, None)
727 denv.input[t.sname] = tdeps
729 # save all the output attributes for each target
731 for attr in savedeps_outputs:
732 v = getattr(t, attr, None)
736 denv.output[t.sname] = tdeps
739 for attr in savedeps_outenv:
741 tdeps[attr] = t.env[attr]
743 denv.outenv[t.sname] = tdeps
745 depsfile = os.path.join(bld.bdir, "sambadeps")
750 def load_samba_deps(bld, tgt_list):
751 '''load a previous set of build dependencies if possible'''
752 depsfile = os.path.join(bld.bdir, "sambadeps")
753 denv = Environment.Environment()
755 debug('deps: checking saved dependencies')
757 if (denv.version != savedeps_version or
758 denv.savedeps_inputs != savedeps_inputs or
759 denv.savedeps_outputs != savedeps_outputs):
764 # check if critical files have changed
765 for f in savedeps_files:
766 if f not in denv.files:
768 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
771 # check if caches are the same
772 for c in savedeps_caches:
773 if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
776 # check inputs are the same
779 for attr in savedeps_inputs:
780 v = getattr(t, attr, None)
783 if t.sname in denv.input:
784 olddeps = denv.input[t.sname]
788 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
791 # put outputs in place
793 if not t.sname in denv.output: continue
794 tdeps = denv.output[t.sname]
796 setattr(t, a, tdeps[a])
798 # put output env vars in place
800 if not t.sname in denv.outenv: continue
801 tdeps = denv.outenv[t.sname]
805 debug('deps: loaded saved dependencies')
810 def check_project_rules(bld):
811 '''check the project rules - ensuring the targets are sane'''
813 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
817 # build a list of task generators we are interested in
821 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
823 t = bld.name_to_obj(tgt, bld.env)
825 print("Target %s of type %s has no task generator" % (tgt, type))
829 add_samba_attributes(bld, tgt_list)
831 if load_samba_deps(bld, tgt_list):
834 print("Checking project rules ...")
836 debug('deps: project rules checking started')
838 expand_subsystem_deps(bld)
839 build_direct_deps(bld, tgt_list)
840 break_dependency_loops(bld, tgt_list)
841 calculate_final_deps(bld, tgt_list, loops)
843 # run the various attribute generators
844 for f in [ build_dependencies, build_includes, add_init_functions ]:
845 debug('deps: project rules checking %s', f)
846 for t in tgt_list: f(t)
848 debug('deps: project rules stage1 completed')
850 #check_orpaned_targets(bld, tgt_list)
852 if not check_duplicate_sources(bld, tgt_list):
853 print("Duplicate sources present - aborting")
856 show_final_deps(bld, tgt_list)
858 debug('deps: project rules checking completed - %u targets checked',
861 save_samba_deps(bld, tgt_list)
863 print("Project rules pass")
866 def CHECK_PROJECT_RULES(bld):
867 '''enable checking of project targets for sanity'''
868 if bld.env.added_project_rules:
870 bld.env.added_project_rules = True
871 bld.add_pre_fun(check_project_rules)
872 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES