waf: Fix the build on openbsd
[Samba.git] / buildtools / wafadmin / Tools / ccroot.py
blob12ef4425210a5d2feb67b3b81611804ee9054fba
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2008 (ita)
5 "base for all c/c++ programs and libraries"
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
19 import config_c # <- necessary for the configuration, do not touch
21 USE_TOP_LEVEL = False
23 def get_cc_version(conf, cc, gcc=False, icc=False):
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)
33 # PY3K: do not touch
34 out = str(out)
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')
42 if icc and out.find('__INTEL_COMPILER') < 0:
43 conf.fatal('Not icc/icpc')
45 k = {}
46 if icc or gcc:
47 out = out.split('\n')
48 import shlex
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
57 def isD(var):
58 return var in k
60 def isT(var):
61 return var in k and k[var] != '0'
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',
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'
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'
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',
111 for i in mp2:
112 if isD(i):
113 conf.env.DEST_CPU = mp2[i]
114 break
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
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"
128 ALL = [ULTRADEBUG, DEBUG, RELEASE, OPTIMIZED, CUSTOM]
130 def scan(self):
131 "look for .h the .cpp need"
132 debug('ccroot: _scan_preprocessor(self, node, env, path_lst)')
134 # TODO waf 1.6 - assume the default input has exactly one file
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)
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)
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)
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')
175 pattern = self.env[tp + '_PATTERN']
176 if not pattern: pattern = '%s'
178 dir, name = os.path.split(self.target)
180 if 'cshlib' in self.features and getattr(self, 'vnum', None):
181 nums = self.vnum.split('.')
182 if self.env.DEST_BINFMT == 'pe':
183 # include the version in the dll file name,
184 # the import lib file name stays unversionned.
185 name = name + '-' + nums[0]
186 elif self.env.DEST_OS == 'openbsd':
187 pattern = '%s.%s' % (pattern, nums[0])
188 if len(nums) >= 2:
189 pattern += '.%s' % nums[1]
191 return os.path.join(dir, pattern % name)
193 @feature('cc', 'cxx')
194 @before('apply_core')
195 def default_cc(self):
196 """compiled_tasks attribute must be set before the '.c->.o' tasks can be created"""
197 Utils.def_attrs(self,
198 includes = '',
199 defines= '',
200 rpaths = '',
201 uselib = '',
202 uselib_local = '',
203 add_objects = '',
204 p_flag_vars = [],
205 p_type_vars = [],
206 compiled_tasks = [],
207 link_task = None)
209 # The only thing we need for cross-compilation is DEST_BINFMT.
210 # At some point, we may reach a case where DEST_BINFMT is not enough, but for now it's sufficient.
211 # Currently, cross-compilation is auto-detected only for the gnu and intel compilers.
212 if not self.env.DEST_BINFMT:
213 # Infer the binary format from the os name.
214 self.env.DEST_BINFMT = Utils.unversioned_sys_platform_to_binary_format(
215 self.env.DEST_OS or Utils.unversioned_sys_platform())
217 if not self.env.BINDIR: self.env.BINDIR = Utils.subst_vars('${PREFIX}/bin', self.env)
218 if not self.env.LIBDIR: self.env.LIBDIR = Utils.subst_vars('${PREFIX}/lib${LIB_EXT}', self.env)
220 @feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib')
221 def apply_verif(self):
222 """no particular order, used for diagnostic"""
223 if not (self.source or getattr(self, 'add_objects', None) or getattr(self, 'uselib_local', None) or getattr(self, 'obj_files', None)):
224 raise Utils.WafError('no source files specified for %s' % self)
225 if not self.target:
226 raise Utils.WafError('no target for %s' % self)
228 # TODO reference the d programs, shlibs in d.py, not here
230 @feature('cprogram', 'dprogram')
231 @after('default_cc')
232 @before('apply_core')
233 def vars_target_cprogram(self):
234 self.default_install_path = self.env.BINDIR
235 self.default_chmod = O755
237 @after('default_cc')
238 @feature('cshlib', 'dshlib')
239 @before('apply_core')
240 def vars_target_cshlib(self):
241 if self.env.DEST_BINFMT == 'pe':
242 # set execute bit on libs to avoid 'permission denied' (issue 283)
243 self.default_chmod = O755
244 self.default_install_path = self.env.BINDIR
245 else:
246 self.default_install_path = self.env.LIBDIR
248 @feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib')
249 @after('apply_link', 'vars_target_cprogram', 'vars_target_cshlib')
250 def default_link_install(self):
251 """you may kill this method to inject your own installation for the first element
252 any other install should only process its own nodes and not those from the others"""
253 if self.install_path:
254 self.bld.install_files(self.install_path, self.link_task.outputs[0], env=self.env, chmod=self.chmod)
256 @feature('cc', 'cxx')
257 @after('apply_type_vars', 'apply_lib_vars', 'apply_core')
258 def apply_incpaths(self):
259 """used by the scanner
260 after processing the uselib for CPPPATH
261 after apply_core because some processing may add include paths
263 lst = []
264 # TODO move the uselib processing out of here
265 for lib in self.to_list(self.uselib):
266 for path in self.env['CPPPATH_' + lib]:
267 if not path in lst:
268 lst.append(path)
269 if preproc.go_absolute:
270 for path in preproc.standard_includes:
271 if not path in lst:
272 lst.append(path)
274 for path in self.to_list(self.includes):
275 if not path in lst:
276 if preproc.go_absolute or not os.path.isabs(path):
277 lst.append(path)
278 else:
279 self.env.prepend_value('CPPPATH', path)
281 for path in lst:
282 node = None
283 if os.path.isabs(path):
284 if preproc.go_absolute:
285 node = self.bld.root.find_dir(path)
286 elif path[0] == '#':
287 node = self.bld.srcnode
288 if len(path) > 1:
289 node = node.find_dir(path[1:])
290 else:
291 node = self.path.find_dir(path)
293 if node:
294 self.env.append_value('INC_PATHS', node)
296 # TODO WAF 1.6
297 if USE_TOP_LEVEL:
298 self.env.append_value('INC_PATHS', self.bld.srcnode)
300 @feature('cc', 'cxx')
301 @after('init_cc', 'init_cxx')
302 @before('apply_lib_vars')
303 def apply_type_vars(self):
304 """before apply_lib_vars because we modify uselib
305 after init_cc and init_cxx because web need p_type_vars
307 for x in self.features:
308 if not x in ['cprogram', 'cstaticlib', 'cshlib']:
309 continue
310 x = x.lstrip('c')
312 # if the type defines uselib to add, add them
313 st = self.env[x + '_USELIB']
314 if st: self.uselib = self.uselib + ' ' + st
316 # each compiler defines variables like 'shlib_CXXFLAGS', 'shlib_LINKFLAGS', etc
317 # so when we make a task generator of the type shlib, CXXFLAGS are modified accordingly
318 for var in self.p_type_vars:
319 compvar = '%s_%s' % (x, var)
320 #print compvar
321 value = self.env[compvar]
322 if value: self.env.append_value(var, value)
324 @feature('cprogram', 'cshlib', 'cstaticlib')
325 @after('apply_core')
326 def apply_link(self):
327 """executes after apply_core for collecting 'compiled_tasks'
328 use a custom linker if specified (self.link='name-of-custom-link-task')"""
329 link = getattr(self, 'link', None)
330 if not link:
331 if 'cstaticlib' in self.features: link = 'static_link'
332 elif 'cxx' in self.features: link = 'cxx_link'
333 else: link = 'cc_link'
335 tsk = self.create_task(link)
336 outputs = [t.outputs[0] for t in self.compiled_tasks]
337 tsk.set_inputs(outputs)
338 tsk.set_outputs(self.path.find_or_declare(get_target_name(self)))
340 self.link_task = tsk
342 @feature('cc', 'cxx')
343 @after('apply_link', 'init_cc', 'init_cxx', 'apply_core')
344 def apply_lib_vars(self):
345 """after apply_link because of 'link_task'
346 after default_cc because of the attribute 'uselib'"""
348 # after 'apply_core' in case if 'cc' if there is no link
350 env = self.env
352 # 1. the case of the libs defined in the project (visit ancestors first)
353 # the ancestors external libraries (uselib) will be prepended
354 self.uselib = self.to_list(self.uselib)
355 names = self.to_list(self.uselib_local)
357 seen = set([])
358 tmp = Utils.deque(names) # consume a copy of the list of names
359 while tmp:
360 lib_name = tmp.popleft()
361 # visit dependencies only once
362 if lib_name in seen:
363 continue
365 y = self.name_to_obj(lib_name)
366 if not y:
367 raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name))
368 y.post()
369 seen.add(lib_name)
371 # object has ancestors to process (shared libraries): add them to the end of the list
372 if getattr(y, 'uselib_local', None):
373 lst = y.to_list(y.uselib_local)
374 if 'cshlib' in y.features or 'cprogram' in y.features:
375 lst = [x for x in lst if not 'cstaticlib' in self.name_to_obj(x).features]
376 tmp.extend(lst)
378 # link task and flags
379 if getattr(y, 'link_task', None):
381 link_name = y.target[y.target.rfind(os.sep) + 1:]
382 if 'cstaticlib' in y.features:
383 env.append_value('STATICLIB', link_name)
384 elif 'cshlib' in y.features or 'cprogram' in y.features:
385 # WARNING some linkers can link against programs
386 env.append_value('LIB', link_name)
388 # the order
389 self.link_task.set_run_after(y.link_task)
391 # for the recompilation
392 dep_nodes = getattr(self.link_task, 'dep_nodes', [])
393 self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
395 # add the link path too
396 tmp_path = y.link_task.outputs[0].parent.bldpath(self.env)
397 if not tmp_path in env['LIBPATH']: env.prepend_value('LIBPATH', tmp_path)
399 # add ancestors uselib too - but only propagate those that have no staticlib
400 for v in self.to_list(y.uselib):
401 if not env['STATICLIB_' + v]:
402 if not v in self.uselib:
403 self.uselib.insert(0, v)
405 # if the library task generator provides 'export_incdirs', add to the include path
406 # the export_incdirs must be a list of paths relative to the other library
407 if getattr(y, 'export_incdirs', None):
408 for x in self.to_list(y.export_incdirs):
409 node = y.path.find_dir(x)
410 if not node:
411 raise Utils.WafError('object %r: invalid folder %r in export_incdirs' % (y.target, x))
412 self.env.append_unique('INC_PATHS', node)
414 # 2. the case of the libs defined outside
415 for x in self.uselib:
416 for v in self.p_flag_vars:
417 val = self.env[v + '_' + x]
418 if val: self.env.append_value(v, val)
420 @feature('cprogram', 'cstaticlib', 'cshlib')
421 @after('init_cc', 'init_cxx', 'apply_link')
422 def apply_objdeps(self):
423 "add the .o files produced by some other object files in the same manner as uselib_local"
424 if not getattr(self, 'add_objects', None): return
426 seen = []
427 names = self.to_list(self.add_objects)
428 while names:
429 x = names[0]
431 # visit dependencies only once
432 if x in seen:
433 names = names[1:]
434 continue
436 # object does not exist ?
437 y = self.name_to_obj(x)
438 if not y:
439 raise Utils.WafError('object %r was not found in uselib_local (required by add_objects %r)' % (x, self.name))
441 # object has ancestors to process first ? update the list of names
442 if getattr(y, 'add_objects', None):
443 added = 0
444 lst = y.to_list(y.add_objects)
445 lst.reverse()
446 for u in lst:
447 if u in seen: continue
448 added = 1
449 names = [u]+names
450 if added: continue # list of names modified, loop
452 # safe to process the current object
453 y.post()
454 seen.append(x)
456 for t in y.compiled_tasks:
457 self.link_task.inputs.extend(t.outputs)
459 @feature('cprogram', 'cshlib', 'cstaticlib')
460 @after('apply_lib_vars')
461 def apply_obj_vars(self):
462 """after apply_lib_vars for uselib"""
463 v = self.env
464 lib_st = v['LIB_ST']
465 staticlib_st = v['STATICLIB_ST']
466 libpath_st = v['LIBPATH_ST']
467 staticlibpath_st = v['STATICLIBPATH_ST']
468 rpath_st = v['RPATH_ST']
470 app = v.append_unique
472 if v['FULLSTATIC']:
473 v.append_value('LINKFLAGS', v['FULLSTATIC_MARKER'])
475 for i in v['RPATH']:
476 if i and rpath_st:
477 app('LINKFLAGS', rpath_st % i)
479 for i in v['LIBPATH']:
480 app('LINKFLAGS', libpath_st % i)
481 app('LINKFLAGS', staticlibpath_st % i)
483 if v['STATICLIB']:
484 v.append_value('LINKFLAGS', v['STATICLIB_MARKER'])
485 k = [(staticlib_st % i) for i in v['STATICLIB']]
486 app('LINKFLAGS', k)
488 # fully static binaries ?
489 if not v['FULLSTATIC']:
490 if v['STATICLIB'] or v['LIB']:
491 v.append_value('LINKFLAGS', v['SHLIB_MARKER'])
493 app('LINKFLAGS', [lib_st % i for i in v['LIB']])
495 @after('apply_link')
496 def process_obj_files(self):
497 if not hasattr(self, 'obj_files'): return
498 for x in self.obj_files:
499 node = self.path.find_resource(x)
500 self.link_task.inputs.append(node)
502 @taskgen
503 def add_obj_file(self, file):
504 """Small example on how to link object files as if they were source
505 obj = bld.create_obj('cc')
506 obj.add_obj_file('foo.o')"""
507 if not hasattr(self, 'obj_files'): self.obj_files = []
508 if not 'process_obj_files' in self.meths: self.meths.append('process_obj_files')
509 self.obj_files.append(file)
511 c_attrs = {
512 'cxxflag' : 'CXXFLAGS',
513 'cflag' : 'CCFLAGS',
514 'ccflag' : 'CCFLAGS',
515 'linkflag' : 'LINKFLAGS',
516 'ldflag' : 'LINKFLAGS',
517 'lib' : 'LIB',
518 'libpath' : 'LIBPATH',
519 'staticlib': 'STATICLIB',
520 'staticlibpath': 'STATICLIBPATH',
521 'rpath' : 'RPATH',
522 'framework' : 'FRAMEWORK',
523 'frameworkpath' : 'FRAMEWORKPATH'
526 @feature('cc', 'cxx')
527 @before('init_cxx', 'init_cc')
528 @before('apply_lib_vars', 'apply_obj_vars', 'apply_incpaths', 'init_cc')
529 def add_extra_flags(self):
530 """case and plural insensitive
531 before apply_obj_vars for processing the library attributes
533 for x in self.__dict__.keys():
534 y = x.lower()
535 if y[-1] == 's':
536 y = y[:-1]
537 if c_attrs.get(y, None):
538 self.env.append_unique(c_attrs[y], getattr(self, x))
540 # ============ the code above must not know anything about import libs ==========
542 @feature('cshlib')
543 @after('apply_link', 'default_cc')
544 @before('apply_lib_vars', 'apply_objdeps', 'default_link_install')
545 def apply_implib(self):
546 """On mswindows, handle dlls and their import libs
547 the .dll.a is the import lib and it is required for linking so it is installed too
549 if not self.env.DEST_BINFMT == 'pe':
550 return
552 self.meths.remove('default_link_install')
554 bindir = self.install_path
555 if not bindir: return
557 # install the dll in the bin dir
558 dll = self.link_task.outputs[0]
559 self.bld.install_files(bindir, dll, self.env, self.chmod)
561 # add linker flags to generate the import lib
562 implib = self.env['implib_PATTERN'] % os.path.split(self.target)[1]
564 implib = dll.parent.find_or_declare(implib)
565 self.link_task.outputs.append(implib)
566 self.bld.install_as('${LIBDIR}/%s' % implib.name, implib, self.env)
568 self.env.append_value('LINKFLAGS', (self.env['IMPLIB_ST'] % implib.bldpath(self.env)).split())
570 # ============ the code above must not know anything about vnum processing on unix platforms =========
572 @feature('cshlib')
573 @after('apply_link')
574 @before('apply_lib_vars', 'default_link_install')
575 def apply_vnum(self):
577 libfoo.so is installed as libfoo.so.1.2.3
579 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'):
580 return
582 self.meths.remove('default_link_install')
584 link = self.link_task
585 nums = self.vnum.split('.')
586 node = link.outputs[0]
588 libname = node.name
589 if libname.endswith('.dylib'):
590 name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum)
591 name2 = libname.replace('.dylib', '.%s.dylib' % nums[0])
592 else:
593 name3 = libname + '.' + self.vnum
594 name2 = libname + '.' + nums[0]
596 if self.env.SONAME_ST:
597 v = self.env.SONAME_ST % name2
598 self.env.append_value('LINKFLAGS', v.split())
600 bld = self.bld
601 nums = self.vnum.split('.')
603 path = self.install_path
604 if not path: return
606 if self.env.DEST_OS == 'openbsd':
607 libname = self.link_task.outputs[0].name
608 bld.install_as('%s%s%s' % (path, os.sep, libname), node, env=self.env)
609 else:
610 bld.install_as(path + os.sep + name3, node, env=self.env)
611 bld.symlink_as(path + os.sep + name2, name3)
612 bld.symlink_as(path + os.sep + libname, name3)
614 # the following task is just to enable execution from the build dir :-/
615 if self.env.DEST_OS != 'openbsd':
616 self.create_task('vnum', node, [node.parent.find_or_declare(name2), node.parent.find_or_declare(name3)])
618 def exec_vnum_link(self):
619 for x in self.outputs:
620 path = x.abspath(self.env)
621 try:
622 os.remove(path)
623 except OSError:
624 pass
626 try:
627 os.symlink(self.inputs[0].name, path)
628 except OSError:
629 return 1
631 cls = Task.task_type_from_func('vnum', func=exec_vnum_link, ext_in='.bin', color='CYAN')
632 cls.quiet = 1
634 # ============ the --as-needed flag should added during the configuration, not at runtime =========
636 @conftest
637 def add_as_needed(conf):
638 if conf.env.DEST_BINFMT == 'elf' and 'gcc' in (conf.env.CXX_NAME, conf.env.CC_NAME):
639 conf.env.append_unique('LINKFLAGS', '--as-needed')