264bdc7f0f6350db145fcdcba22aff27a94b306a
[obnox/samba/samba-obnox.git] / buildtools / wafadmin / Tools / ccroot.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2008 (ita)
4
5 "base for all c/c++ programs and libraries"
6
7 import os, sys, re
8 import TaskGen, Task, Utils, preproc, Logs, Build, Options
9 from Logs import error, debug, warn
10 from Utils import md5
11 from TaskGen import taskgen, after, before, feature
12 from Constants import *
13 from Configure import conftest
14 try:
15         from cStringIO import StringIO
16 except ImportError:
17         from io import StringIO
18
19 import config_c # <- necessary for the configuration, do not touch
20
21 USE_TOP_LEVEL = False
22
23 def get_cc_version(conf, cc, gcc=False, icc=False):
24
25         cmd = cc + ['-dM', '-E', '-']
26         try:
27                 p = Utils.pproc.Popen(cmd, stdin=Utils.pproc.PIPE, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE)
28                 p.stdin.write('\n')
29                 out = p.communicate()[0]
30         except:
31                 conf.fatal('could not determine the compiler version %r' % cmd)
32
33         # PY3K: do not touch
34         out = str(out)
35
36         if gcc:
37                 if out.find('__INTEL_COMPILER') >= 0:
38                         conf.fatal('The intel compiler pretends to be gcc')
39                 if out.find('__GNUC__') < 0:
40                         conf.fatal('Could not determine the compiler type')
41
42         if icc and out.find('__INTEL_COMPILER') < 0:
43                 conf.fatal('Not icc/icpc')
44
45         k = {}
46         if icc or gcc:
47                 out = out.split('\n')
48                 import shlex
49
50                 for line in out:
51                         lst = shlex.split(line)
52                         if len(lst)>2:
53                                 key = lst[1]
54                                 val = lst[2]
55                                 k[key] = val
56
57                 def isD(var):
58                         return var in k
59
60                 def isT(var):
61                         return var in k and k[var] != '0'
62
63                 # Some documentation is available at http://predef.sourceforge.net
64                 # The names given to DEST_OS must match what Utils.unversioned_sys_platform() returns.
65                 mp1 = {
66                         '__linux__'   : 'linux',
67                         '__GNU__'     : 'gnu',
68                         '__FreeBSD__' : 'freebsd',
69                         '__NetBSD__'  : 'netbsd',
70                         '__OpenBSD__' : 'openbsd',
71                         '__sun'       : 'sunos',
72                         '__hpux'      : 'hpux',
73                         '__sgi'       : 'irix',
74                         '_AIX'        : 'aix',
75                         '__CYGWIN__'  : 'cygwin',
76                         '__MSYS__'    : 'msys',
77                         '_UWIN'       : 'uwin',
78                         '_WIN64'      : 'win32',
79                         '_WIN32'      : 'win32',
80                         '__POWERPC__' : 'powerpc',
81                         }
82
83                 for i in mp1:
84                         if isD(i):
85                                 conf.env.DEST_OS = mp1[i]
86                                 break
87                 else:
88                         if isD('__APPLE__') and isD('__MACH__'):
89                                 conf.env.DEST_OS = 'darwin'
90                         elif isD('__unix__'): # unix must be tested last as it's a generic fallback
91                                 conf.env.DEST_OS = 'generic'
92
93                 if isD('__ELF__'):
94                         conf.env.DEST_BINFMT = 'elf'
95                 elif isD('__WINNT__') or isD('__CYGWIN__'):
96                         conf.env.DEST_BINFMT = 'pe'
97                 elif isD('__APPLE__'):
98                         conf.env.DEST_BINFMT = 'mac-o'
99
100                 mp2 = {
101                                 '__x86_64__'  : 'x86_64',
102                                 '__i386__'    : 'x86',
103                                 '__ia64__'    : 'ia',
104                                 '__mips__'    : 'mips',
105                                 '__sparc__'   : 'sparc',
106                                 '__alpha__'   : 'alpha',
107                                 '__arm__'     : 'arm',
108                                 '__hppa__'    : 'hppa',
109                                 '__powerpc__' : 'powerpc',
110                                 }
111                 for i in mp2:
112                         if isD(i):
113                                 conf.env.DEST_CPU = mp2[i]
114                                 break
115
116                 debug('ccroot: dest platform: ' + ' '.join([conf.env[x] or '?' for x in ('DEST_OS', 'DEST_BINFMT', 'DEST_CPU')]))
117                 conf.env['CC_VERSION'] = (k['__GNUC__'], k['__GNUC_MINOR__'], k['__GNUC_PATCHLEVEL__'])
118         return k
119
120 class DEBUG_LEVELS:
121         """Will disappear in waf 1.6"""
122         ULTRADEBUG = "ultradebug"
123         DEBUG = "debug"
124         RELEASE = "release"
125         OPTIMIZED = "optimized"
126         CUSTOM = "custom"
127
128         ALL = [ULTRADEBUG, DEBUG, RELEASE, OPTIMIZED, CUSTOM]
129
130 def scan(self):
131         "look for .h the .cpp need"
132         debug('ccroot: _scan_preprocessor(self, node, env, path_lst)')
133
134         # TODO waf 1.6 - assume the default input has exactly one file
135
136         if len(self.inputs) == 1:
137                 node = self.inputs[0]
138                 (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS'])
139                 if Logs.verbose:
140                         debug('deps: deps for %s: %r; unresolved %r', str(node), nodes, names)
141                 return (nodes, names)
142
143         all_nodes = []
144         all_names = []
145         seen = set()
146         for node in self.inputs:
147                 (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS'])
148                 if Logs.verbose:
149                         debug('deps: deps for %s: %r; unresolved %r', str(node), nodes, names)
150                 for x in nodes:
151                         if id(x) in seen: continue
152                         seen.add(id(x))
153                         all_nodes.append(x)
154                 for x in names:
155                         if not x in all_names:
156                                 all_names.append(x)
157         return (all_nodes, all_names)
158
159 class ccroot_abstract(TaskGen.task_gen):
160         "Parent class for programs and libraries in languages c, c++ and moc (Qt)"
161         def __init__(self, *k, **kw):
162                 # COMPAT remove in waf 1.6 TODO
163                 if len(k) > 1:
164                         k = list(k)
165                         if k[1][0] != 'c':
166                                 k[1] = 'c' + k[1]
167                 TaskGen.task_gen.__init__(self, *k, **kw)
168
169 def get_target_name(self):
170         tp = 'program'
171         for x in self.features:
172                 if x in ['cshlib', 'cstaticlib']:
173                         tp = x.lstrip('c')
174
175         pattern = self.env[tp + '_PATTERN']
176         if not pattern: pattern = '%s'
177
178         dir, name = os.path.split(self.target)
179
180         if self.env.DEST_BINFMT == 'pe' and getattr(self, 'vnum', None) and 'cshlib' in self.features:
181                 # include the version in the dll file name,
182                 # the import lib file name stays unversionned.
183                 name = name + '-' + self.vnum.split('.')[0]
184
185         return os.path.join(dir, pattern % name)
186
187 @feature('cc', 'cxx')
188 @before('apply_core')
189 def default_cc(self):
190         """compiled_tasks attribute must be set before the '.c->.o' tasks can be created"""
191         Utils.def_attrs(self,
192                 includes = '',
193                 defines= '',
194                 rpaths = '',
195                 uselib = '',
196                 uselib_local = '',
197                 add_objects = '',
198                 p_flag_vars = [],
199                 p_type_vars = [],
200                 compiled_tasks = [],
201                 link_task = None)
202
203         # The only thing we need for cross-compilation is DEST_BINFMT.
204         # At some point, we may reach a case where DEST_BINFMT is not enough, but for now it's sufficient.
205         # Currently, cross-compilation is auto-detected only for the gnu and intel compilers.
206         if not self.env.DEST_BINFMT:
207                 # Infer the binary format from the os name.
208                 self.env.DEST_BINFMT = Utils.unversioned_sys_platform_to_binary_format(
209                         self.env.DEST_OS or Utils.unversioned_sys_platform())
210
211         if not self.env.BINDIR: self.env.BINDIR = Utils.subst_vars('${PREFIX}/bin', self.env)
212         if not self.env.LIBDIR: self.env.LIBDIR = Utils.subst_vars('${PREFIX}/lib${LIB_EXT}', self.env)
213
214 @feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib')
215 def apply_verif(self):
216         """no particular order, used for diagnostic"""
217         if not (self.source or getattr(self, 'add_objects', None) or getattr(self, 'uselib_local', None) or getattr(self, 'obj_files', None)):
218                 raise Utils.WafError('no source files specified for %s' % self)
219         if not self.target:
220                 raise Utils.WafError('no target for %s' % self)
221
222 # TODO reference the d programs, shlibs in d.py, not here
223
224 @feature('cprogram', 'dprogram')
225 @after('default_cc')
226 @before('apply_core')
227 def vars_target_cprogram(self):
228         self.default_install_path = self.env.BINDIR
229         self.default_chmod = O755
230
231 @after('default_cc')
232 @feature('cshlib', 'dshlib')
233 @before('apply_core')
234 def vars_target_cshlib(self):
235         if self.env.DEST_BINFMT == 'pe':
236                 #   set execute bit on libs to avoid 'permission denied' (issue 283)
237                 self.default_chmod = O755
238                 self.default_install_path = self.env.BINDIR
239         else:
240                 self.default_install_path = self.env.LIBDIR
241
242 @feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib')
243 @after('apply_link', 'vars_target_cprogram', 'vars_target_cshlib')
244 def default_link_install(self):
245         """you may kill this method to inject your own installation for the first element
246         any other install should only process its own nodes and not those from the others"""
247         if self.install_path:
248                 self.bld.install_files(self.install_path, self.link_task.outputs[0], env=self.env, chmod=self.chmod)
249
250 @feature('cc', 'cxx')
251 @after('apply_type_vars', 'apply_lib_vars', 'apply_core')
252 def apply_incpaths(self):
253         """used by the scanner
254         after processing the uselib for CPPPATH
255         after apply_core because some processing may add include paths
256         """
257         lst = []
258         # TODO move the uselib processing out of here
259         for lib in self.to_list(self.uselib):
260                 for path in self.env['CPPPATH_' + lib]:
261                         if not path in lst:
262                                 lst.append(path)
263         if preproc.go_absolute:
264                 for path in preproc.standard_includes:
265                         if not path in lst:
266                                 lst.append(path)
267
268         for path in self.to_list(self.includes):
269                 if not path in lst:
270                         if preproc.go_absolute or not os.path.isabs(path):
271                                 lst.append(path)
272                         else:
273                                 self.env.prepend_value('CPPPATH', path)
274
275         for path in lst:
276                 node = None
277                 if os.path.isabs(path):
278                         if preproc.go_absolute:
279                                 node = self.bld.root.find_dir(path)
280                 elif path[0] == '#':
281                         node = self.bld.srcnode
282                         if len(path) > 1:
283                                 node = node.find_dir(path[1:])
284                 else:
285                         node = self.path.find_dir(path)
286
287                 if node:
288                         self.env.append_value('INC_PATHS', node)
289
290         # TODO WAF 1.6
291         if USE_TOP_LEVEL:
292                 self.env.append_value('INC_PATHS', self.bld.srcnode)
293
294 @feature('cc', 'cxx')
295 @after('init_cc', 'init_cxx')
296 @before('apply_lib_vars')
297 def apply_type_vars(self):
298         """before apply_lib_vars because we modify uselib
299         after init_cc and init_cxx because web need p_type_vars
300         """
301         for x in self.features:
302                 if not x in ['cprogram', 'cstaticlib', 'cshlib']:
303                         continue
304                 x = x.lstrip('c')
305
306                 # if the type defines uselib to add, add them
307                 st = self.env[x + '_USELIB']
308                 if st: self.uselib = self.uselib + ' ' + st
309
310                 # each compiler defines variables like 'shlib_CXXFLAGS', 'shlib_LINKFLAGS', etc
311                 # so when we make a task generator of the type shlib, CXXFLAGS are modified accordingly
312                 for var in self.p_type_vars:
313                         compvar = '%s_%s' % (x, var)
314                         #print compvar
315                         value = self.env[compvar]
316                         if value: self.env.append_value(var, value)
317
318 @feature('cprogram', 'cshlib', 'cstaticlib')
319 @after('apply_core')
320 def apply_link(self):
321         """executes after apply_core for collecting 'compiled_tasks'
322         use a custom linker if specified (self.link='name-of-custom-link-task')"""
323         link = getattr(self, 'link', None)
324         if not link:
325                 if 'cstaticlib' in self.features: link = 'static_link'
326                 elif 'cxx' in self.features: link = 'cxx_link'
327                 else: link = 'cc_link'
328
329         tsk = self.create_task(link)
330         outputs = [t.outputs[0] for t in self.compiled_tasks]
331         tsk.set_inputs(outputs)
332         tsk.set_outputs(self.path.find_or_declare(get_target_name(self)))
333
334         self.link_task = tsk
335
336 @feature('cc', 'cxx')
337 @after('apply_link', 'init_cc', 'init_cxx', 'apply_core')
338 def apply_lib_vars(self):
339         """after apply_link because of 'link_task'
340         after default_cc because of the attribute 'uselib'"""
341
342         # after 'apply_core' in case if 'cc' if there is no link
343
344         env = self.env
345
346         # 1. the case of the libs defined in the project (visit ancestors first)
347         # the ancestors external libraries (uselib) will be prepended
348         self.uselib = self.to_list(self.uselib)
349         names = self.to_list(self.uselib_local)
350
351         seen = set([])
352         tmp = Utils.deque(names) # consume a copy of the list of names
353         while tmp:
354                 lib_name = tmp.popleft()
355                 # visit dependencies only once
356                 if lib_name in seen:
357                         continue
358
359                 y = self.name_to_obj(lib_name)
360                 if not y:
361                         raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name))
362                 y.post()
363                 seen.add(lib_name)
364
365                 # object has ancestors to process (shared libraries): add them to the end of the list
366                 if getattr(y, 'uselib_local', None):
367                         lst = y.to_list(y.uselib_local)
368                         if 'cshlib' in y.features or 'cprogram' in y.features:
369                                 lst = [x for x in lst if not 'cstaticlib' in self.name_to_obj(x).features]
370                         tmp.extend(lst)
371
372                 # link task and flags
373                 if getattr(y, 'link_task', None):
374
375                         link_name = y.target[y.target.rfind(os.sep) + 1:]
376                         if 'cstaticlib' in y.features:
377                                 env.append_value('STATICLIB', link_name)
378                         elif 'cshlib' in y.features or 'cprogram' in y.features:
379                                 # WARNING some linkers can link against programs
380                                 env.append_value('LIB', link_name)
381
382                         # the order
383                         self.link_task.set_run_after(y.link_task)
384
385                         # for the recompilation
386                         dep_nodes = getattr(self.link_task, 'dep_nodes', [])
387                         self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
388
389                         # add the link path too
390                         tmp_path = y.link_task.outputs[0].parent.bldpath(self.env)
391                         if not tmp_path in env['LIBPATH']: env.prepend_value('LIBPATH', tmp_path)
392
393                 # add ancestors uselib too - but only propagate those that have no staticlib
394                 for v in self.to_list(y.uselib):
395                         if not env['STATICLIB_' + v]:
396                                 if not v in self.uselib:
397                                         self.uselib.insert(0, v)
398
399                 # if the library task generator provides 'export_incdirs', add to the include path
400                 # the export_incdirs must be a list of paths relative to the other library
401                 if getattr(y, 'export_incdirs', None):
402                         for x in self.to_list(y.export_incdirs):
403                                 node = y.path.find_dir(x)
404                                 if not node:
405                                         raise Utils.WafError('object %r: invalid folder %r in export_incdirs' % (y.target, x))
406                                 self.env.append_unique('INC_PATHS', node)
407
408         # 2. the case of the libs defined outside
409         for x in self.uselib:
410                 for v in self.p_flag_vars:
411                         val = self.env[v + '_' + x]
412                         if val: self.env.append_value(v, val)
413
414 @feature('cprogram', 'cstaticlib', 'cshlib')
415 @after('init_cc', 'init_cxx', 'apply_link')
416 def apply_objdeps(self):
417         "add the .o files produced by some other object files in the same manner as uselib_local"
418         if not getattr(self, 'add_objects', None): return
419
420         seen = []
421         names = self.to_list(self.add_objects)
422         while names:
423                 x = names[0]
424
425                 # visit dependencies only once
426                 if x in seen:
427                         names = names[1:]
428                         continue
429
430                 # object does not exist ?
431                 y = self.name_to_obj(x)
432                 if not y:
433                         raise Utils.WafError('object %r was not found in uselib_local (required by add_objects %r)' % (x, self.name))
434
435                 # object has ancestors to process first ? update the list of names
436                 if getattr(y, 'add_objects', None):
437                         added = 0
438                         lst = y.to_list(y.add_objects)
439                         lst.reverse()
440                         for u in lst:
441                                 if u in seen: continue
442                                 added = 1
443                                 names = [u]+names
444                         if added: continue # list of names modified, loop
445
446                 # safe to process the current object
447                 y.post()
448                 seen.append(x)
449
450                 for t in y.compiled_tasks:
451                         self.link_task.inputs.extend(t.outputs)
452
453 @feature('cprogram', 'cshlib', 'cstaticlib')
454 @after('apply_lib_vars')
455 def apply_obj_vars(self):
456         """after apply_lib_vars for uselib"""
457         v = self.env
458         lib_st           = v['LIB_ST']
459         staticlib_st     = v['STATICLIB_ST']
460         libpath_st       = v['LIBPATH_ST']
461         staticlibpath_st = v['STATICLIBPATH_ST']
462         rpath_st         = v['RPATH_ST']
463
464         app = v.append_unique
465
466         if v['FULLSTATIC']:
467                 v.append_value('LINKFLAGS', v['FULLSTATIC_MARKER'])
468
469         for i in v['RPATH']:
470                 if i and rpath_st:
471                         app('LINKFLAGS', rpath_st % i)
472
473         for i in v['LIBPATH']:
474                 app('LINKFLAGS', libpath_st % i)
475                 app('LINKFLAGS', staticlibpath_st % i)
476
477         if v['STATICLIB']:
478                 v.append_value('LINKFLAGS', v['STATICLIB_MARKER'])
479                 k = [(staticlib_st % i) for i in v['STATICLIB']]
480                 app('LINKFLAGS', k)
481
482         # fully static binaries ?
483         if not v['FULLSTATIC']:
484                 if v['STATICLIB'] or v['LIB']:
485                         v.append_value('LINKFLAGS', v['SHLIB_MARKER'])
486
487         app('LINKFLAGS', [lib_st % i for i in v['LIB']])
488
489 @after('apply_link')
490 def process_obj_files(self):
491         if not hasattr(self, 'obj_files'): return
492         for x in self.obj_files:
493                 node = self.path.find_resource(x)
494                 self.link_task.inputs.append(node)
495
496 @taskgen
497 def add_obj_file(self, file):
498         """Small example on how to link object files as if they were source
499         obj = bld.create_obj('cc')
500         obj.add_obj_file('foo.o')"""
501         if not hasattr(self, 'obj_files'): self.obj_files = []
502         if not 'process_obj_files' in self.meths: self.meths.append('process_obj_files')
503         self.obj_files.append(file)
504
505 c_attrs = {
506 'cxxflag' : 'CXXFLAGS',
507 'cflag' : 'CCFLAGS',
508 'ccflag' : 'CCFLAGS',
509 'linkflag' : 'LINKFLAGS',
510 'ldflag' : 'LINKFLAGS',
511 'lib' : 'LIB',
512 'libpath' : 'LIBPATH',
513 'staticlib': 'STATICLIB',
514 'staticlibpath': 'STATICLIBPATH',
515 'rpath' : 'RPATH',
516 'framework' : 'FRAMEWORK',
517 'frameworkpath' : 'FRAMEWORKPATH'
518 }
519
520 @feature('cc', 'cxx')
521 @before('init_cxx', 'init_cc')
522 @before('apply_lib_vars', 'apply_obj_vars', 'apply_incpaths', 'init_cc')
523 def add_extra_flags(self):
524         """case and plural insensitive
525         before apply_obj_vars for processing the library attributes
526         """
527         for x in self.__dict__.keys():
528                 y = x.lower()
529                 if y[-1] == 's':
530                         y = y[:-1]
531                 if c_attrs.get(y, None):
532                         self.env.append_unique(c_attrs[y], getattr(self, x))
533
534 # ============ the code above must not know anything about import libs ==========
535
536 @feature('cshlib')
537 @after('apply_link', 'default_cc')
538 @before('apply_lib_vars', 'apply_objdeps', 'default_link_install')
539 def apply_implib(self):
540         """On mswindows, handle dlls and their import libs
541         the .dll.a is the import lib and it is required for linking so it is installed too
542         """
543         if not self.env.DEST_BINFMT == 'pe':
544                 return
545
546         self.meths.remove('default_link_install')
547
548         bindir = self.install_path
549         if not bindir: return
550
551         # install the dll in the bin dir
552         dll = self.link_task.outputs[0]
553         self.bld.install_files(bindir, dll, self.env, self.chmod)
554
555         # add linker flags to generate the import lib
556         implib = self.env['implib_PATTERN'] % os.path.split(self.target)[1]
557
558         implib = dll.parent.find_or_declare(implib)
559         self.link_task.outputs.append(implib)
560         self.bld.install_as('${LIBDIR}/%s' % implib.name, implib, self.env)
561
562         self.env.append_value('LINKFLAGS', (self.env['IMPLIB_ST'] % implib.bldpath(self.env)).split())
563
564 # ============ the code above must not know anything about vnum processing on unix platforms =========
565
566 @feature('cshlib')
567 @after('apply_link')
568 @before('apply_lib_vars', 'default_link_install')
569 def apply_vnum(self):
570         """
571         libfoo.so is installed as libfoo.so.1.2.3
572         """
573         if not getattr(self, 'vnum', '') or not 'cshlib' in self.features or os.name != 'posix' or self.env.DEST_BINFMT not in ('elf', 'mac-o'):
574                 return
575
576         self.meths.remove('default_link_install')
577
578         link = self.link_task
579         nums = self.vnum.split('.')
580         node = link.outputs[0]
581
582         libname = node.name
583         if libname.endswith('.dylib'):
584                 name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum)
585                 name2 = libname.replace('.dylib', '.%s.dylib' % nums[0])
586         else:
587                 name3 = libname + '.' + self.vnum
588                 name2 = libname + '.' + nums[0]
589
590         if self.env.SONAME_ST:
591                 v = self.env.SONAME_ST % name2
592                 self.env.append_value('LINKFLAGS', v.split())
593
594         bld = self.bld
595         nums = self.vnum.split('.')
596
597         path = self.install_path
598         if not path: return
599
600         if self.env.DEST_OS == 'openbsd':
601                 bld.install_as(path + os.sep + name2, node, env=self.env, chmod=self.link_task.chmod)
602         else:
603                 bld.install_as(path + os.sep + name3, node, env=self.env)
604                 bld.symlink_as(path + os.sep + name2, name3)
605                 bld.symlink_as(path + os.sep + libname, name3)
606
607         # the following task is just to enable execution from the build dir :-/
608         self.create_task('vnum', node, [node.parent.find_or_declare(name2), node.parent.find_or_declare(name3)])
609
610 def exec_vnum_link(self):
611         for x in self.outputs:
612                 path = x.abspath(self.env)
613                 try:
614                         os.remove(path)
615                 except OSError:
616                         pass
617
618                 try:
619                         os.symlink(self.inputs[0].name, path)
620                 except OSError:
621                         return 1
622
623 cls = Task.task_type_from_func('vnum', func=exec_vnum_link, ext_in='.bin', color='CYAN')
624 cls.quiet = 1
625
626 # ============ the --as-needed flag should added during the configuration, not at runtime =========
627
628 @conftest
629 def add_as_needed(conf):
630         if conf.env.DEST_BINFMT == 'elf' and 'gcc' in (conf.env.CXX_NAME, conf.env.CC_NAME):
631                 conf.env.append_unique('LINKFLAGS', '--as-needed')
632