build: waf quicktest nearly works
[samba.git] / buildtools / wafsamba / samba_deps.py
1 # Samba automatic dependency handling and project rules
2
3 import Build, os, re, Environment
4 from samba_utils import *
5 from samba_autoconf import *
6
7 @conf
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)
13
14
15 def TARGET_ALIAS(bld, target, alias):
16     '''define an alias for a target name'''
17     cache = LOCAL_CACHE(bld, 'TARGET_ALIAS')
18     bld.ASSERT(alias not in cache, "Target alias %s already set" % alias)
19     cache[alias] = target
20 Build.BuildContext.TARGET_ALIAS = TARGET_ALIAS
21
22
23 def EXPAND_ALIAS(bld, target):
24     '''expand a target name via an alias'''
25     aliases = LOCAL_CACHE(bld, 'TARGET_ALIAS')
26     if target in aliases:
27         return aliases[target]
28     return target
29 Build.BuildContext.EXPAND_ALIAS = EXPAND_ALIAS
30
31
32 def expand_subsystem_deps(bld):
33     '''expand the reverse dependencies resulting from subsystem
34        attributes of modules'''
35     subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
36     aliases    = LOCAL_CACHE(bld, 'TARGET_ALIAS')
37     targets    = LOCAL_CACHE(bld, 'TARGET_TYPE')
38
39     for s in subsystems:
40         if s in aliases:
41             s = aliases[s]
42         bld.ASSERT(s in targets, "Subsystem target %s not declared" % s)
43         type = targets[s]
44         if type == 'DISABLED' or type == 'EMPTY':
45             continue
46
47         t = bld.name_to_obj(s, bld.env)
48         bld.ASSERT(t is not None, "Subsystem target %s not found" % s)
49         for d in subsystems[s]:
50             type = targets[d['TARGET']]
51             if type != 'DISABLED' and type != 'EMPTY':
52                 t.samba_deps_extended.append(d['TARGET'])
53                 t2 = bld.name_to_obj(d['TARGET'], bld.env)
54                 t2.samba_includes_extended.extend(t.samba_includes_extended)
55                 t2.samba_deps_extended.extend(t.samba_deps_extended)
56         t.samba_deps_extended = unique_list(t.samba_deps_extended)
57
58
59
60 def build_dependencies(self):
61     '''This builds the dependency list for a target. It runs after all the targets are declared
62
63     The reason this is not just done in the SAMBA_*() rules is that we have no way of knowing
64     the full dependency list for a target until we have all of the targets declared.
65     '''
66
67     # we only should add extra library and object deps on libraries and binaries
68     if not self.samba_type in ['LIBRARY', 'BINARY', 'PYTHON']:
69         return
70
71     # we need to link against:
72
73     #  1) any direct system libs
74     #  2) any indirect system libs that come from subsystem dependencies
75     #  3) any direct local libs
76     #  4) any indirect local libs that come from subsystem dependencies
77     #  5) any direct objects
78     #  6) any indirect objects that come from subsystem dependencies
79
80     self.uselib        = list(self.final_syslibs)
81     self.uselib_local  = list(self.final_libs)
82     self.add_objects   = list(self.final_objects)
83
84     debug('deps: computed dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
85           self.sname, self.uselib, self.uselib_local, self.add_objects)
86
87
88
89 def build_includes(self):
90     '''This builds the right set of includes for a target.
91
92     One tricky part of this is that the includes= attribute for a
93     target needs to use paths which are relative to that targets
94     declaration directory (which we can get at via t.path).
95
96     The way this works is the includes list gets added as
97     samba_includes in the main build task declaration. Then this
98     function runs after all of the tasks are declared, and it
99     processes the samba_includes attribute to produce a includes=
100     attribute
101     '''
102
103     if getattr(self, 'samba_includes', None) is None:
104         return
105
106     bld = self.bld
107
108     inc_deps = self.includes_objects
109
110     includes = []
111
112     # maybe add local includes
113     if getattr(self, 'local_include', True) == True and getattr(self, 'local_include_first', True):
114         includes.append('.')
115
116     includes.extend(self.samba_includes_extended)
117
118     if 'EXTRA_INCLUDES' in bld.env:
119         includes.extend(bld.env['EXTRA_INCLUDES'])
120
121     includes.append('#')
122
123     inc_set = set()
124     inc_abs = []
125
126     for d in inc_deps:
127         t = bld.name_to_obj(d, bld.env)
128         bld.ASSERT(t is not None, "Unable to find dependency %s for %s" % (d, self.sname))
129         inclist = getattr(t, 'samba_includes_extended', [])
130         if getattr(t, 'local_include', True) == True:
131             inclist.append('.')
132         if inclist == []:
133             continue
134         tpath = t.samba_abspath
135         for inc in inclist:
136             npath = tpath + '/' + inc
137             if not npath in inc_set:
138                 inc_abs.append(npath)
139                 inc_set.add(npath)
140
141     mypath = self.path.abspath(bld.env)
142     for inc in inc_abs:
143         relpath = os_path_relpath(inc, mypath)
144         includes.append(relpath)
145
146     if getattr(self, 'local_include', True) == True and not getattr(self, 'local_include_first', True):
147         includes.append('.')
148
149     self.includes = unique_list(includes)
150     debug('deps: includes for target %s: includes=%s',
151           self.sname, self.includes)
152
153
154
155 def add_init_functions(self):
156     '''This builds the right set of init functions'''
157
158     bld = self.bld
159
160     subsystems = LOCAL_CACHE(bld, 'INIT_FUNCTIONS')
161
162     modules = []
163     if self.sname in subsystems:
164         modules.append(self.sname)
165
166     m = getattr(self, 'samba_modules', None)
167     if m is not None:
168         modules.extend(TO_LIST(m))
169
170     m = getattr(self, 'samba_subsystem', None)
171     if m is not None:
172         modules.append(m)
173
174     if modules == []:
175         return
176
177     sentinal = getattr(self, 'init_function_sentinal', 'NULL')
178
179     cflags = getattr(self, 'samba_cflags', [])[:]
180     for m in modules:
181         bld.ASSERT(m in subsystems,
182                    "No init_function defined for module '%s' in target '%s'" % (m, self.sname))
183         init_fn_list = []
184         for d in subsystems[m]:
185             init_fn_list.append(d['INIT_FUNCTION'])
186         cflags.append('-DSTATIC_%s_MODULES=%s' % (m, ','.join(init_fn_list) + ',' + sentinal))
187     self.ccflags = cflags
188
189
190
191 def check_duplicate_sources(bld, tgt_list):
192     '''see if we are compiling the same source file into multiple
193     subsystem targets for the same library or binary'''
194
195     debug('deps: checking for duplicate sources')
196
197     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
198
199     for t in tgt_list:
200         if not targets[t.sname] in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
201             continue
202
203         sources = []
204         for obj in t.add_objects:
205             t2 = t.bld.name_to_obj(obj, bld.env)
206             obj_sources = getattr(t2, 'source', '')
207             if obj_sources == '': continue
208             tpath = os_path_relpath(t2.path.abspath(bld.env), t.env['BUILD_DIRECTORY'] + '/default')
209             obj_sources = bld.SUBDIR(tpath, obj_sources)
210             sources.append( { 'dep':obj, 'src':set(TO_LIST(obj_sources)) } )
211             #debug('deps: dependency expansion for target %s add_object %s: %s',
212             #      t.sname, obj, obj_sources)
213             for s in sources:
214                 for s2 in sources:
215                     if s['dep'] == s2['dep']: continue
216                     common = s['src'].intersection(s2['src'])
217                     if common:
218                         bld.ASSERT(False,
219                                    "Target %s has duplicate source files in %s and %s : %s" % (t.sname,
220                                                                                                s['dep'], s2['dep'],
221                                                                                                common))
222
223 def check_orpaned_targets(bld, tgt_list):
224     '''check if any build targets are orphaned'''
225
226     target_dict = LOCAL_CACHE(bld, 'TARGET_TYPE')
227
228     debug('deps: checking for orphaned targets')
229
230     for t in tgt_list:
231         if getattr(t, 'samba_used', False) == True:
232             continue
233         type = target_dict[t.sname]
234         if not type in ['BINARY', 'LIBRARY', 'MODULE', 'ET', 'PYTHON']:
235             if re.search('^PIDL_', t.sname) is None:
236                 print "Target %s of type %s is unused by any other target" % (t.sname, type)
237
238
239 def show_final_deps(bld, tgt_list):
240     '''show the final dependencies for all targets'''
241
242     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
243
244     for t in tgt_list:
245         if not targets[t.sname] in ['LIBRARY', 'BINARY', 'PYTHON']:
246             continue
247         debug('deps: final dependencies for target %s: uselib=%s uselib_local=%s add_objects=%s',
248               t.sname, t.uselib, t.uselib_local, t.add_objects)
249
250
251 def add_samba_attributes(bld, tgt_list):
252     '''ensure a target has a the required samba attributes'''
253
254     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
255
256     for t in tgt_list:
257         if t.name != '':
258             t.sname = t.name
259         else:
260             t.sname = t.target
261         t.samba_type = targets[t.sname]
262         t.samba_abspath = t.path.abspath(bld.env)
263         t.samba_deps_extended = t.samba_deps[:]
264         t.samba_includes_extended = TO_LIST(t.samba_includes)[:]
265         t.ccflags = getattr(t, 'samba_cflags', '')
266
267 def build_direct_deps(bld, tgt_list):
268     '''build the direct_objects and direct_libs sets for each target'''
269
270     targets  = LOCAL_CACHE(bld, 'TARGET_TYPE')
271     global_deps = bld.env.GLOBAL_DEPENDENCIES
272
273     for t in tgt_list:
274         t.direct_objects = set()
275         t.direct_libs = set()
276         t.direct_syslibs = set()
277         deps = t.samba_deps_extended
278         deps.extend(global_deps)
279         for d in deps:
280             d = EXPAND_ALIAS(bld, d)
281             if not d in targets:
282                 print "Unknown dependency %s in %s" % (d, t.sname)
283                 raise
284             if targets[d] in [ 'EMPTY', 'DISABLED' ]:
285                 continue
286             if targets[d] == 'SYSLIB':
287                 t.direct_syslibs.add(d)
288                 continue
289             t2 = bld.name_to_obj(d, bld.env)
290             if t2 is None:
291                 print "no task %s type %s" % (d, targets[d])
292             if t2.samba_type in [ 'LIBRARY', 'MODULE' ]:
293                 t.direct_libs.add(d)
294             elif t2.samba_type in [ 'SUBSYSTEM', 'ASN1', 'PYTHON' ]:
295                 t.direct_objects.add(d)
296     debug('deps: built direct dependencies')
297
298
299
300 def indirect_libs(bld, t, chain):
301     '''recursively calculate the indirect library dependencies for a target
302
303     An indirect library is a library that results from a dependency on
304     a subsystem
305     '''
306
307     ret = getattr(t, 'indirect_libs', None)
308     if ret is not None:
309         return ret
310
311     ret = set()
312     for obj in t.direct_objects:
313         if obj in chain:
314             continue
315         chain.add(obj)
316         t2 = bld.name_to_obj(obj, bld.env)
317         r2 = indirect_libs(bld, t2, chain)
318         chain.remove(obj)
319         ret = ret.union(t2.direct_libs)
320         ret = ret.union(r2)
321
322     for obj in t.indirect_objects:
323         if obj in chain:
324             continue
325         chain.add(obj)
326         t2 = bld.name_to_obj(obj, bld.env)
327         r2 = indirect_libs(bld, t2, chain)
328         chain.remove(obj)
329         ret = ret.union(t2.direct_libs)
330         ret = ret.union(r2)
331
332     t.indirect_libs = ret
333
334     return ret
335
336
337 def indirect_syslibs(bld, t, chain):
338     '''recursively calculate the indirect system library dependencies for a target
339
340     An indirect syslib results from a subsystem dependency
341     '''
342
343     ret = getattr(t, 'indirect_syslibs', None)
344     if ret is not None:
345         return ret
346     ret = set()
347     for obj in t.direct_objects:
348         if obj in chain:
349             continue
350         chain.add(obj)
351         t2 = bld.name_to_obj(obj, bld.env)
352         r2 = indirect_syslibs(bld, t2, chain)
353         chain.remove(obj)
354         ret = ret.union(t2.direct_syslibs)
355         ret = ret.union(r2)
356
357     t.indirect_syslibs = ret
358     return ret
359
360
361 def indirect_objects(bld, t, chain):
362     '''recursively calculate the indirect object dependencies for a target
363
364     indirect objects are the set of objects from expanding the
365     subsystem dependencies
366     '''
367
368     ret = getattr(t, 'indirect_objects', None)
369     if ret is not None: return ret
370
371     ret = set()
372     for lib in t.direct_objects:
373         if lib in chain:
374             continue
375         chain.add(lib)
376         t2 = bld.name_to_obj(lib, bld.env)
377         r2 = indirect_objects(bld, t2, chain)
378         chain.remove(lib)
379         ret = ret.union(t2.direct_objects)
380         ret = ret.union(r2)
381
382     t.indirect_objects = ret
383     return ret
384
385
386 def expanded_targets(bld, t, chain):
387     '''recursively calculate the expanded targets for a target
388
389     expanded objects are the set of objects, libraries and syslibs
390     from expanding the subsystem dependencies, library dependencies
391     and syslib dependencies
392     '''
393
394     ret = getattr(t, 'expanded_targets', None)
395     if ret is not None: return ret
396
397     ret = t.direct_objects.copy()
398     ret = ret.union(t.direct_libs)
399     ret = ret.union(t.direct_syslibs)
400
401     direct = ret.copy()
402
403     for d in direct:
404         if d in chain: continue
405         chain.add(d)
406         t2 = bld.name_to_obj(d, bld.env)
407         if t2 is None: continue
408         r2 = expanded_targets(bld, t2, chain)
409         chain.remove(d)
410         ret = ret.union(r2)
411
412     if t.sname in ret:
413         ret.remove(t.sname)
414
415     t.expanded_targets = ret
416     return ret
417
418
419 def expanded_targets2(bld, t, chain):
420     '''recursively calculate the expanded targets for a target
421
422     expanded objects are the set of objects from expanding the
423     subsystem dependencies and library dependencies
424     '''
425
426     ret = getattr(t, 'expanded_targets2', None)
427     if ret is not None: return ret
428
429     ret = t.final_objects.copy()
430
431     for attr in [ 'final_objects', 'final_libs' ]:
432         f = getattr(t, attr, set())
433         for d in f.copy():
434             if d in chain:
435                 continue
436             chain.add(d)
437             t2 = bld.name_to_obj(d, bld.env)
438             if t2 is None: continue
439             r2 = expanded_targets2(bld, t2, chain)
440             chain.remove(d)
441             ret = ret.union(r2)
442
443     if t.sname in ret:
444         ret.remove(t.sname)
445
446     t.expanded_targets2 = ret
447     return ret
448
449
450 def includes_objects(bld, t, chain):
451     '''recursively calculate the includes object dependencies for a target
452
453     includes dependencies come from either library or object dependencies
454     '''
455     ret = getattr(t, 'includes_objects', None)
456     if ret is not None:
457         return ret
458
459     ret = t.direct_objects.copy()
460     ret = ret.union(t.direct_libs)
461
462     for obj in t.direct_objects:
463         if obj in chain:
464             continue
465         chain.add(obj)
466         t2 = bld.name_to_obj(obj, bld.env)
467         r2 = includes_objects(bld, t2, chain)
468         chain.remove(obj)
469         ret = ret.union(t2.direct_objects)
470         ret = ret.union(r2)
471
472     for lib in t.direct_libs:
473         if lib in chain:
474             continue
475         chain.add(lib)
476         t2 = bld.name_to_obj(lib, bld.env)
477         r2 = includes_objects(bld, t2, chain)
478         chain.remove(lib)
479         ret = ret.union(t2.direct_objects)
480         ret = ret.union(r2)
481
482     t.includes_objects = ret
483     return ret
484
485
486 def build_indirect_deps(bld, tgt_list):
487     '''build the indirect_objects and indirect_libs sets for each target'''
488     for t in tgt_list:
489         indirect_objects(bld, t, set())
490         indirect_libs(bld, t, set())
491         indirect_syslibs(bld, t, set())
492         includes_objects(bld, t, set())
493         expanded_targets(bld, t, set())
494     debug('deps: built indirect dependencies')
495
496
497 def re_expand2(bld, tgt_list):
498     for t in tgt_list:
499         t.expanded_targets2 = None
500     for type in ['BINARY','LIBRARY','PYTHON']:
501         for t in tgt_list:
502             if t.samba_type == type:
503                 expanded_targets2(bld, t, set())
504     for t in tgt_list:
505         expanded_targets2(bld, t, set())
506
507
508 def calculate_final_deps(bld, tgt_list):
509     '''calculate the final library and object dependencies'''
510     for t in tgt_list:
511         # start with the maximum possible list
512         t.final_syslibs = t.direct_syslibs.union(t.indirect_syslibs)
513         t.final_libs    = t.direct_libs.union(t.indirect_libs)
514         t.final_objects = t.direct_objects.union(t.indirect_objects)
515
516     for t in tgt_list:
517         # don't depend on ourselves
518         if t.sname in t.final_libs:
519             t.final_libs.remove(t.sname)
520         if t.sname in t.final_objects:
521             t.final_objects.remove(t.sname)
522
523     re_expand2(bld, tgt_list)
524
525     loops = {}
526
527     # find any library loops
528     for t in tgt_list:
529         if t.samba_type in ['LIBRARY', 'PYTHON']:
530             for l in t.final_libs.copy():
531                 t2 = bld.name_to_obj(l, bld.env)
532                 if t.sname in t2.final_libs:
533                     debug('deps: removing library loop %s<->%s', t.sname, l)
534                     t2.final_libs.remove(t.sname)
535                     loops[t2.sname] = t.sname;
536
537     re_expand2(bld, tgt_list)
538
539     for type in ['BINARY']:
540         while True:
541             changed = False
542             for t in tgt_list:
543                 if t.samba_type != type: continue
544                 # if we will indirectly link to a target then we don't need it
545                 new = t.final_objects.copy()
546                 for l in t.final_libs:
547                     t2 = bld.name_to_obj(l, bld.env)
548                     dup = new.intersection(t2.expanded_targets2)
549                     if dup:
550                         debug('deps: removing dups from %s: %s also in %s %s',
551                               t.sname, dup, t2.samba_type, l)
552                         new = new.difference(dup)
553                         changed = True
554                 if changed:
555                     t.final_objects = new
556                     break
557             if not changed:
558                 break
559     debug('deps: removed duplicate dependencies')
560
561
562 ######################################################################
563 # this provides a way to save our dependency calculations between runs
564 savedeps_version = 1
565 savedeps_inputs  = ['samba_deps', 'samba_includes', 'local_include', 'local_include_first', 'samba_cflags']
566 savedeps_outputs = ['uselib', 'uselib_local', 'add_objects', 'includes', 'ccflags']
567 savedeps_caches  = ['GLOBAL_DEPENDENCIES', 'TARGET_ALIAS', 'TARGET_TYPE', 'INIT_FUNCTIONS']
568
569 def save_samba_deps(bld, tgt_list):
570     '''save the dependency calculations between builds, to make
571        further builds faster'''
572     denv = Environment.Environment()
573
574     denv.version = savedeps_version
575     denv.savedeps_inputs = savedeps_inputs
576     denv.savedeps_outputs = savedeps_outputs
577     denv.input = {}
578     denv.output = {}
579     denv.caches = {}
580
581     for c in savedeps_caches:
582         denv.caches[c] = LOCAL_CACHE(bld, c)
583
584     for t in tgt_list:
585         # save all the input attributes for each target
586         tdeps = {}
587         for attr in savedeps_inputs:
588             v = getattr(t, attr, None)
589             if v is not None:
590                 tdeps[attr] = v
591         if tdeps != {}:
592             denv.input[t.sname] = tdeps
593
594         # save all the output attributes for each target
595         tdeps = {}
596         for attr in savedeps_outputs:
597             v = getattr(t, attr, None)
598             if v is not None:
599                 tdeps[attr] = v
600         if tdeps != {}:
601             denv.output[t.sname] = tdeps
602
603     depsfile = os.path.join(bld.bdir, "sambadeps")
604     denv.store(depsfile)
605
606
607 def load_samba_deps(bld, tgt_list):
608     '''load a previous set of build dependencies if possible'''
609     depsfile = os.path.join(bld.bdir, "sambadeps")
610     denv = Environment.Environment()
611     try:
612         debug('deps: checking saved dependencies')
613         denv.load(depsfile)
614         if (denv.version != savedeps_version or
615             denv.savedeps_inputs != savedeps_inputs or
616             denv.savedeps_outputs != savedeps_outputs):
617             return False
618     except:
619         return False
620
621     # check if caches are the same
622     for c in savedeps_caches:
623         if c not in denv.caches or denv.caches[c] != LOCAL_CACHE(bld, c):
624             return False
625
626     # check inputs are the same
627     for t in tgt_list:
628         tdeps = {}
629         for attr in savedeps_inputs:
630             v = getattr(t, attr, None)
631             if v is not None:
632                 tdeps[attr] = v
633         if t.sname in denv.input:
634             olddeps = denv.input[t.sname]
635         else:
636             olddeps = {}
637         if tdeps != olddeps:
638             #print '%s: \ntdeps=%s \nodeps=%s' % (t.sname, tdeps, olddeps)
639             return False
640
641     # put outputs in place
642     for t in tgt_list:
643         if not t.sname in denv.output: continue
644         tdeps = denv.output[t.sname]
645         for a in tdeps:
646             setattr(t, a, tdeps[a])
647
648     debug('deps: loaded saved dependencies')
649     return True
650
651
652 def check_project_rules(bld):
653     '''check the project rules - ensuring the targets are sane'''
654
655     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
656
657     # build a list of task generators we are interested in
658     tgt_list = []
659     for tgt in targets:
660         type = targets[tgt]
661         if not type in ['SUBSYSTEM', 'MODULE', 'BINARY', 'LIBRARY', 'ASN1', 'PYTHON']:
662             continue
663         t = bld.name_to_obj(tgt, bld.env)
664         tgt_list.append(t)
665
666     add_samba_attributes(bld, tgt_list)
667
668     if load_samba_deps(bld, tgt_list):
669         return
670
671     debug('deps: project rules checking started')
672
673     expand_subsystem_deps(bld)
674     build_direct_deps(bld, tgt_list)
675     build_indirect_deps(bld, tgt_list)
676     calculate_final_deps(bld, tgt_list)
677
678     # run the various attribute generators
679     for f in [ build_dependencies, build_includes, add_init_functions ]:
680         debug('deps: project rules checking %s', f)
681         for t in tgt_list: f(t)
682
683     debug('deps: project rules stage1 completed')
684
685     #check_orpaned_targets(bld, tgt_list)
686     #check_duplicate_sources(bld, tgt_list)
687     show_final_deps(bld, tgt_list)
688
689     debug('deps: project rules checking completed - %u targets checked',
690           len(tgt_list))
691
692     save_samba_deps(bld, tgt_list)
693
694
695 def CHECK_PROJECT_RULES(bld):
696     '''enable checking of project targets for sanity'''
697     if bld.env.added_project_rules:
698         return
699     bld.env.added_project_rules = True
700     bld.add_pre_fun(check_project_rules)
701 Build.BuildContext.CHECK_PROJECT_RULES = CHECK_PROJECT_RULES
702
703