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 only should add extra library and object deps on libraries and binaries
70 if not self.samba_type in ['LIBRARY', 'BINARY', 'PYTHON']:
73 # we need to link against:
75 # 1) any direct system libs
76 # 2) any indirect system libs that come from subsystem dependencies
77 # 3) any direct local libs
78 # 4) any indirect local libs that come from subsystem dependencies
79 # 5) any direct objects
80 # 6) any indirect objects that come from subsystem dependencies
82 self.uselib = list(self.final_syslibs)
83 self.uselib_local = list(self.final_libs)
84 self.add_objects = list(self.final_objects)
86 debug('deps: computed dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
87 self.sname, self.uselib, self.uselib_local, self.add_objects)
91 def build_includes(self):
92 '''This builds the right set of includes for a target.
94 One tricky part of this is that the includes= attribute for a
95 target needs to use paths which are relative to that targets
96 declaration directory (which we can get at via t.path).
98 The way this works is the includes list gets added as
99 samba_includes in the main build task declaration. Then this
100 function runs after all of the tasks are declared, and it
101 processes the samba_includes attribute to produce a includes=
105 if getattr(self, 'samba_includes', None) is None:
110 inc_deps = self.includes_objects
114 # maybe add local includes
115 if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
118 includes.extend(self.samba_includes_extended)
120 if 'EXTRA_INCLUDES' in bld.env:
121 includes.extend(bld.env['EXTRA_INCLUDES'])
129 t = bld.name_to_obj(d, bld.env)
130 bld.ASSERT(t is not None, "Unable to find dependency %s for %s" % (d, self.sname))
131 inclist = getattr(t, 'samba_includes_extended', [])
132 if getattr(t, 'local_include', True) == True:
136 tpath = t.samba_abspath
138 npath = tpath + '/' + inc
139 if not npath in inc_set:
140 inc_abs.append(npath)
143 mypath = self.path.abspath(bld.env)
145 relpath = os_path_relpath(inc, mypath)
146 includes.append(relpath)
148 if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
151 # now transform the includes list to be relative to the top directory
152 # which is represented by '#' in waf. This allows waf to cache the
153 # includes lists more efficiently
157 # some are already top based
158 includes_top.append(i)
160 absinc = os.path.join(self.path.abspath(), i)
161 relinc = os_path_relpath(absinc, self.bld.srcnode.abspath())
162 includes_top.append('#' + relinc)
164 self.includes = unique_list(includes_top)
165 debug('deps: includes for target %s: includes=%s',
166 self.sname, self.includes)
171 def add_init_functions(self):
172 '''This builds the right set of init functions'''
176 subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
178 # cope with the separated object lists from BINARY and LIBRARY targets
180 if sname.endswith('.objlist'):
184 if sname in subsystems:
185 modules.append(sname)
187 m = getattr(self, 'samba_modules', None)
189 modules.extend(TO_LIST(m))
191 m = getattr(self, 'samba_subsystem', None)
198 sentinal = getattr(self, 'init_function_sentinal', 'NULL')
200 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
202 cflags = getattr(self, 'samba_cflags', [])[:]
204 bld.ASSERT(m in subsystems,
205 "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
207 for d in subsystems[m]:
208 if targets[d['TARGET']] != 'DISABLED':
209 init_fn_list.append(d['INIT_FUNCTION'])
210 if init_fn_list == []:
211 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, sentinal))
213 cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
214 self.ccflags = cflags
218 def check_duplicate_sources(bld, tgt_list):
219 '''see if we are compiling the same source file into multiple
220 subsystem targets for the same library or binary'''
222 debug('deps: checking for duplicate sources')
224 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
230 # this was useful for finding problems with the autogenerated rules
233 sources = TO_LIST(getattr(t, 'source', ''))
235 bname = os.path.basename(s)
236 if bname in base_list:
237 print "Suspicious duplicate name %s in %s" % (bname, t.sname)
244 obj_sources = getattr(t, 'source', '')
245 tpath = os_path_relpath(t.path.abspath(bld.env), t.env['BUILD_DIRECTORY'] + '/default')
246 obj_sources = bld.SUBDIR(tpath, obj_sources)
247 t.samba_source_set = set(TO_LIST(obj_sources))
250 if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
254 for obj in t.add_objects:
255 t2 = t.bld.name_to_obj(obj, bld.env)
256 source_set = getattr(t2, 'samba_source_set', set())
257 sources.append( { 'dep':obj, 'src':source_set} )
260 if s['dep'] == s2['dep']: continue
261 common = s['src'].intersection(s2['src'])
262 if common.difference(seen):
263 print("Target %s has duplicate source files in %s and %s : %s" % (t.sname,
266 seen = seen.union(common)
271 def check_orpaned_targets(bld, tgt_list):
272 '''check if any build targets are orphaned'''
274 target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
276 debug('deps: checking for orphaned targets')
279 if getattr(t, 'samba_used', False) == True:
281 type = target_dict[t.sname]
282 if not type in ['BINARY', 'LIBRARY', 'MODULE', 'ET', 'PYTHON']:
283 if re.search('^PIDL_', t.sname) is None:
284 print "Target %s of type %s is unused by any other target" % (t.sname, type)
287 def show_final_deps(bld, tgt_list):
288 '''show the final dependencies for all targets'''
290 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
293 if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
295 debug('deps: final dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
296 t.sname, t.uselib, t.uselib_local, t.add_objects)
299 def add_samba_attributes(bld, tgt_list):
300 '''ensure a target has a the required samba attributes'''
302 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
309 t.samba_type = targets[t.sname]
310 t.samba_abspath = t.path.abspath(bld.env)
311 t.samba_deps_extended = t.samba_deps[:]
312 t.samba_includes_extended = TO_LIST(t.samba_includes)[:]
313 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 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)
330 print "Unknown dependency %s in %s" % (d, t.sname)
332 if targets[d] in [ 'EMPTY', 'DISABLED' ]:
334 if targets[d] == 'SYSLIB':
335 t.direct_syslibs.add(d)
337 t2 = bld.name_to_obj(d, bld.env)
339 print "no task %s type %s" % (d, targets[d])
340 if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
342 elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
343 t.direct_objects.add(d)
344 debug('deps: built direct dependencies')
348 def indirect_libs(bld, t, chain):
349 '''recursively calculate the indirect library dependencies for a target
351 An indirect library is a library that results from a dependency on
355 ret = getattr(t, 'indirect_libs', None)
360 for obj in t.direct_objects:
364 t2 = bld.name_to_obj(obj, bld.env)
365 r2 = indirect_libs(bld, t2, chain)
367 ret = ret.union(t2.direct_libs)
370 for obj in t.indirect_objects:
374 t2 = bld.name_to_obj(obj, bld.env)
375 r2 = indirect_libs(bld, t2, chain)
377 ret = ret.union(t2.direct_libs)
380 t.indirect_libs = ret
385 def indirect_syslibs(bld, t, chain):
386 '''recursively calculate the indirect system library dependencies for a target
388 An indirect syslib results from a subsystem dependency
391 ret = getattr(t, 'indirect_syslibs', None)
395 for obj in t.direct_objects:
399 t2 = bld.name_to_obj(obj, bld.env)
400 r2 = indirect_syslibs(bld, t2, chain)
402 ret = ret.union(t2.direct_syslibs)
405 t.indirect_syslibs = ret
409 def indirect_objects(bld, t, chain):
410 '''recursively calculate the indirect object dependencies for a target
412 indirect objects are the set of objects from expanding the
413 subsystem dependencies
416 ret = getattr(t, 'indirect_objects', None)
417 if ret is not None: return ret
420 for lib in t.direct_objects:
424 t2 = bld.name_to_obj(lib, bld.env)
425 r2 = indirect_objects(bld, t2, chain)
427 ret = ret.union(t2.direct_objects)
430 t.indirect_objects = ret
434 def expanded_targets(bld, t, chain):
435 '''recursively calculate the expanded targets for a target
437 expanded objects are the set of objects, libraries and syslibs
438 from expanding the subsystem dependencies, library dependencies
439 and syslib dependencies
442 ret = getattr(t, 'expanded_targets', None)
443 if ret is not None: return ret
445 ret = t.direct_objects.copy()
446 ret = ret.union(t.direct_libs)
447 ret = ret.union(t.direct_syslibs)
452 if d in chain: continue
454 t2 = bld.name_to_obj(d, bld.env)
455 if t2 is None: continue
456 r2 = expanded_targets(bld, t2, chain)
463 t.expanded_targets = ret
467 def expanded_targets2(bld, t, chain):
468 '''recursively calculate the expanded targets for a target
470 expanded objects are the set of objects from expanding the
471 subsystem dependencies and library dependencies
474 ret = getattr(t, 'expanded_targets2', None)
475 if ret is not None: return ret
477 ret = t.final_objects.copy()
479 for attr in [ 'final_objects', 'final_libs' ]:
480 f = getattr(t, attr, set())
485 t2 = bld.name_to_obj(d, bld.env)
486 if t2 is None: continue
487 r2 = expanded_targets2(bld, t2, chain)
494 t.expanded_targets2 = ret
498 def includes_objects(bld, t, chain):
499 '''recursively calculate the includes object dependencies for a target
501 includes dependencies come from either library or object dependencies
503 ret = getattr(t, 'includes_objects', None)
507 ret = t.direct_objects.copy()
508 ret = ret.union(t.direct_libs)
510 for obj in t.direct_objects:
514 t2 = bld.name_to_obj(obj, bld.env)
515 r2 = includes_objects(bld, t2, chain)
517 ret = ret.union(t2.direct_objects)
520 for lib in t.direct_libs:
524 t2 = bld.name_to_obj(lib, bld.env)
525 r2 = includes_objects(bld, t2, chain)
527 ret = ret.union(t2.direct_objects)
530 t.includes_objects = ret
534 def build_indirect_deps(bld, tgt_list):
535 '''build the indirect_objects and indirect_libs sets for each target'''
537 indirect_objects(bld, t, set())
538 indirect_libs(bld, t, set())
539 indirect_syslibs(bld, t, set())
540 includes_objects(bld, t, set())
541 expanded_targets(bld, t, set())
542 debug('deps: built indirect dependencies')
545 def re_expand2(bld, tgt_list):
547 t.expanded_targets2 = None
548 for type in ['BINARY','LIBRARY','PYTHON']:
550 if t.samba_type == type:
551 expanded_targets2(bld, t, set())
553 expanded_targets2(bld, t, set())
556 def calculate_final_deps(bld, tgt_list):
557 '''calculate the final library and object dependencies'''
559 # start with the maximum possible list
560 t.final_syslibs = t.direct_syslibs.union(t.indirect_syslibs)
561 t.final_libs = t.direct_libs.union(t.indirect_libs)
562 t.final_objects = t.direct_objects.union(t.indirect_objects)
565 # don't depend on ourselves
566 if t.sname in t.final_libs:
567 t.final_libs.remove(t.sname)
568 if t.sname in t.final_objects:
569 t.final_objects.remove(t.sname)
571 re_expand2(bld, tgt_list)
575 # find any library loops
577 if t.samba_type in ['LIBRARY', 'PYTHON']:
578 for l in t.final_libs.copy():
579 t2 = bld.name_to_obj(l, bld.env)
580 if t.sname in t2.final_libs:
581 # we could break this in either direction. If one of the libraries
582 # has a version number, and will this be distributed publicly, then
583 # we should make it the lower level library in the DAG
584 debug('deps: removing library loop %s<->%s', t.sname, l)
585 t2.final_libs.remove(t.sname)
586 loops[t2.sname] = t.sname;
588 re_expand2(bld, tgt_list)
590 for type in ['BINARY']:
594 if t.samba_type != type: continue
595 # if we will indirectly link to a target then we don't need it
596 new = t.final_objects.copy()
597 for l in t.final_libs:
598 t2 = bld.name_to_obj(l, bld.env)
599 dup = new.intersection(t2.expanded_targets2)
601 debug('deps: removing dups from %s: %s also in %s %s',
602 t.sname, dup, t2.samba_type, l)
603 new = new.difference(dup)
606 t.final_objects = new
611 # we now need to make corrections for any library loops we broke up
612 # any target that depended on the target of the loop and doesn't
613 # depend on the source of the loop needs to get the loop source added
614 for type in ['BINARY','PYTHON']:
616 if t.samba_type != type: continue
618 if loop in t.final_libs and loops[loop] not in t.final_libs:
619 t.final_libs.add(loops[loop])
621 debug('deps: removed duplicate dependencies')
624 ######################################################################
625 # this provides a way to save our dependency calculations between runs
627 savedeps_inputs = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags', 'source']
628 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
629 savedeps_outenv = ['INC_PATHS']
630 savedeps_caches = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS']
631 savedeps_files = ['buildtools/wafsamba/samba_deps.py']
633 def save_samba_deps(bld, tgt_list):
634 '''save the dependency calculations between builds, to make
635 further builds faster'''
636 denv = Environment.Environment()
638 denv.version = savedeps_version
639 denv.savedeps_inputs = savedeps_inputs
640 denv.savedeps_outputs = savedeps_outputs
647 for f in savedeps_files:
648 denv.files[f] = os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime
650 for c in savedeps_caches:
651 denv.caches[c] = LOCAL_CACHE(bld, c)
654 # save all the input attributes for each target
656 for attr in savedeps_inputs:
657 v = getattr(t, attr, None)
661 denv.input[t.sname] = tdeps
663 # save all the output attributes for each target
665 for attr in savedeps_outputs:
666 v = getattr(t, attr, None)
670 denv.output[t.sname] = tdeps
673 for attr in savedeps_outenv:
675 tdeps[attr] = t.env[attr]
677 denv.outenv[t.sname] = tdeps
679 depsfile = os.path.join(bld.bdir, "sambadeps")
683 def load_samba_deps(bld, tgt_list):
684 '''load a previous set of build dependencies if possible'''
685 depsfile = os.path.join(bld.bdir, "sambadeps")
686 denv = Environment.Environment()
688 debug('deps: checking saved dependencies')
690 if (denv.version != savedeps_version or
691 denv.savedeps_inputs != savedeps_inputs or
692 denv.savedeps_outputs != savedeps_outputs):
697 # check if critical files have changed
698 for f in savedeps_files:
699 if f not in denv.files:
701 if denv.files[f] != os.stat(os.path.join(bld.srcnode.abspath(), f)).st_mtime:
704 # check if caches are the same
705 for c in savedeps_caches:
706 if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
709 # check inputs are the same
712 for attr in savedeps_inputs:
713 v = getattr(t, attr, None)
716 if t.sname in denv.input:
717 olddeps = denv.input[t.sname]
721 #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
724 # put outputs in place
726 if not t.sname in denv.output: continue
727 tdeps = denv.output[t.sname]
729 setattr(t, a, tdeps[a])
731 # put output env vars in place
733 if not t.sname in denv.outenv: continue
734 tdeps = denv.outenv[t.sname]
738 debug('deps: loaded saved dependencies')
742 def check_project_rules(bld):
743 '''check the project rules - ensuring the targets are sane'''
745 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
747 # build a list of task generators we are interested in
751 if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
753 t = bld.name_to_obj(tgt, bld.env)
755 print "Target %s of type %s has no task generator" % (tgt, type)
759 add_samba_attributes(bld, tgt_list)
761 if load_samba_deps(bld, tgt_list):
764 print "Checking project rules ..."
766 debug('deps: project rules checking started')
768 expand_subsystem_deps(bld)
769 build_direct_deps(bld, tgt_list)
770 build_indirect_deps(bld, tgt_list)
771 calculate_final_deps(bld, tgt_list)
773 # run the various attribute generators
774 for f in [ build_dependencies, build_includes, add_init_functions ]:
775 debug('deps: project rules checking %s', f)
776 for t in tgt_list: f(t)
778 debug('deps: project rules stage1 completed')
780 #check_orpaned_targets(bld, tgt_list)
782 if not check_duplicate_sources(bld, tgt_list):
783 print "Duplicate sources present - aborting"
786 show_final_deps(bld, tgt_list)
788 debug('deps: project rules checking completed - %u targets checked',
791 save_samba_deps(bld, tgt_list)
793 print "Project rules pass"
796 def CHECK_PROJECT_RULES(bld):
797 '''enable checking of project targets for sanity'''
798 if bld.env.added_project_rules:
800 bld.env.added_project_rules = True
801 bld.add_pre_fun(check_project_rules)
802 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES