3 # Thomas Nagy, 2005-2008 (ita)
5 "base for all c/c++ programs and libraries"
8 import TaskGen, Task, Utils, preproc, Logs, Build, Options
9 from Logs import error, debug, warn
11 from TaskGen import taskgen, after, before, feature
12 from Constants import *
13 from Configure import conftest
15 from cStringIO import StringIO
17 from io import StringIO
19 import config_c # <- necessary for the configuration, do not touch
23 def get_cc_version(conf, cc, gcc=False, icc=False):
25 cmd = cc + ['-dM', '-E', '-']
27 p = Utils.pproc.Popen(cmd, stdin=Utils.pproc.PIPE, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE)
29 out = p.communicate()[0]
31 conf.fatal('could not determine the compiler version %r' % cmd)
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')
51 lst = shlex.split(line)
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.
66 '__linux__' : 'linux',
68 '__FreeBSD__' : 'freebsd',
69 '__NetBSD__' : 'netbsd',
70 '__OpenBSD__' : 'openbsd',
75 '__CYGWIN__' : 'cygwin',
80 '__POWERPC__' : 'powerpc',
85 conf.env.DEST_OS = mp1[i]
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'
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'
101 '__x86_64__' : 'x86_64',
105 '__sparc__' : 'sparc',
106 '__alpha__' : 'alpha',
109 '__powerpc__' : 'powerpc',
113 conf.env.DEST_CPU = mp2[i]
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__'])
121 """Will disappear in waf 1.6"""
122 ULTRADEBUG = "ultradebug"
125 OPTIMIZED = "optimized"
128 ALL = [ULTRADEBUG, DEBUG, RELEASE, OPTIMIZED, CUSTOM]
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'])
140 debug('deps: deps for %s: %r; unresolved %r', str(node), nodes, names)
141 return (nodes, names)
146 for node in self.inputs:
147 (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS'])
149 debug('deps: deps for %s: %r; unresolved %r', str(node), nodes, names)
151 if id(x) in seen: continue
155 if not x in all_names:
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
167 TaskGen.task_gen.__init__(self, *k, **kw)
169 def get_target_name(self):
171 for x in self.features:
172 if x in ['cshlib', 'cstaticlib']:
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.%s' % (pattern, nums[0], nums[1])
189 return os.path.join(dir, pattern % name)
191 @feature('cc', 'cxx')
192 @before('apply_core')
193 def default_cc(self):
194 """compiled_tasks attribute must be set before the '.c->.o' tasks can be created"""
195 Utils.def_attrs(self,
207 # The only thing we need for cross-compilation is DEST_BINFMT.
208 # At some point, we may reach a case where DEST_BINFMT is not enough, but for now it's sufficient.
209 # Currently, cross-compilation is auto-detected only for the gnu and intel compilers.
210 if not self.env.DEST_BINFMT:
211 # Infer the binary format from the os name.
212 self.env.DEST_BINFMT = Utils.unversioned_sys_platform_to_binary_format(
213 self.env.DEST_OS or Utils.unversioned_sys_platform())
215 if not self.env.BINDIR: self.env.BINDIR = Utils.subst_vars('${PREFIX}/bin', self.env)
216 if not self.env.LIBDIR: self.env.LIBDIR = Utils.subst_vars('${PREFIX}/lib${LIB_EXT}', self.env)
218 @feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib')
219 def apply_verif(self):
220 """no particular order, used for diagnostic"""
221 if not (self.source or getattr(self, 'add_objects', None) or getattr(self, 'uselib_local', None) or getattr(self, 'obj_files', None)):
222 raise Utils.WafError('no source files specified for %s' % self)
224 raise Utils.WafError('no target for %s' % self)
226 # TODO reference the d programs, shlibs in d.py, not here
228 @feature('cprogram', 'dprogram')
230 @before('apply_core')
231 def vars_target_cprogram(self):
232 self.default_install_path = self.env.BINDIR
233 self.default_chmod = O755
236 @feature('cshlib', 'dshlib')
237 @before('apply_core')
238 def vars_target_cshlib(self):
239 if self.env.DEST_BINFMT == 'pe':
240 # set execute bit on libs to avoid 'permission denied' (issue 283)
241 self.default_chmod = O755
242 self.default_install_path = self.env.BINDIR
244 self.default_install_path = self.env.LIBDIR
246 @feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib')
247 @after('apply_link', 'vars_target_cprogram', 'vars_target_cshlib')
248 def default_link_install(self):
249 """you may kill this method to inject your own installation for the first element
250 any other install should only process its own nodes and not those from the others"""
251 if self.install_path:
252 self.bld.install_files(self.install_path, self.link_task.outputs[0], env=self.env, chmod=self.chmod)
254 @feature('cc', 'cxx')
255 @after('apply_type_vars', 'apply_lib_vars', 'apply_core')
256 def apply_incpaths(self):
257 """used by the scanner
258 after processing the uselib for CPPPATH
259 after apply_core because some processing may add include paths
262 # TODO move the uselib processing out of here
263 for lib in self.to_list(self.uselib):
264 for path in self.env['CPPPATH_' + lib]:
267 if preproc.go_absolute:
268 for path in preproc.standard_includes:
272 for path in self.to_list(self.includes):
274 if preproc.go_absolute or not os.path.isabs(path):
277 self.env.prepend_value('CPPPATH', path)
281 if os.path.isabs(path):
282 if preproc.go_absolute:
283 node = self.bld.root.find_dir(path)
285 node = self.bld.srcnode
287 node = node.find_dir(path[1:])
289 node = self.path.find_dir(path)
292 self.env.append_value('INC_PATHS', node)
296 self.env.append_value('INC_PATHS', self.bld.srcnode)
298 @feature('cc', 'cxx')
299 @after('init_cc', 'init_cxx')
300 @before('apply_lib_vars')
301 def apply_type_vars(self):
302 """before apply_lib_vars because we modify uselib
303 after init_cc and init_cxx because web need p_type_vars
305 for x in self.features:
306 if not x in ['cprogram', 'cstaticlib', 'cshlib']:
310 # if the type defines uselib to add, add them
311 st = self.env[x + '_USELIB']
312 if st: self.uselib = self.uselib + ' ' + st
314 # each compiler defines variables like 'shlib_CXXFLAGS', 'shlib_LINKFLAGS', etc
315 # so when we make a task generator of the type shlib, CXXFLAGS are modified accordingly
316 for var in self.p_type_vars:
317 compvar = '%s_%s' % (x, var)
319 value = self.env[compvar]
320 if value: self.env.append_value(var, value)
322 @feature('cprogram', 'cshlib', 'cstaticlib')
324 def apply_link(self):
325 """executes after apply_core for collecting 'compiled_tasks'
326 use a custom linker if specified (self.link='name-of-custom-link-task')"""
327 link = getattr(self, 'link', None)
329 if 'cstaticlib' in self.features: link = 'static_link'
330 elif 'cxx' in self.features: link = 'cxx_link'
331 else: link = 'cc_link'
333 tsk = self.create_task(link)
334 outputs = [t.outputs[0] for t in self.compiled_tasks]
335 tsk.set_inputs(outputs)
336 tsk.set_outputs(self.path.find_or_declare(get_target_name(self)))
340 @feature('cc', 'cxx')
341 @after('apply_link', 'init_cc', 'init_cxx', 'apply_core')
342 def apply_lib_vars(self):
343 """after apply_link because of 'link_task'
344 after default_cc because of the attribute 'uselib'"""
346 # after 'apply_core' in case if 'cc' if there is no link
350 # 1. the case of the libs defined in the project (visit ancestors first)
351 # the ancestors external libraries (uselib) will be prepended
352 self.uselib = self.to_list(self.uselib)
353 names = self.to_list(self.uselib_local)
356 tmp = Utils.deque(names) # consume a copy of the list of names
358 lib_name = tmp.popleft()
359 # visit dependencies only once
363 y = self.name_to_obj(lib_name)
365 raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name))
369 # object has ancestors to process (shared libraries): add them to the end of the list
370 if getattr(y, 'uselib_local', None):
371 lst = y.to_list(y.uselib_local)
372 if 'cshlib' in y.features or 'cprogram' in y.features:
373 lst = [x for x in lst if not 'cstaticlib' in self.name_to_obj(x).features]
376 # link task and flags
377 if getattr(y, 'link_task', None):
379 link_name = y.target[y.target.rfind(os.sep) + 1:]
380 if 'cstaticlib' in y.features:
381 env.append_value('STATICLIB', link_name)
382 elif 'cshlib' in y.features or 'cprogram' in y.features:
383 # WARNING some linkers can link against programs
384 env.append_value('LIB', link_name)
387 self.link_task.set_run_after(y.link_task)
389 # for the recompilation
390 dep_nodes = getattr(self.link_task, 'dep_nodes', [])
391 self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
393 # add the link path too
394 tmp_path = y.link_task.outputs[0].parent.bldpath(self.env)
395 if not tmp_path in env['LIBPATH']: env.prepend_value('LIBPATH', tmp_path)
397 # add ancestors uselib too - but only propagate those that have no staticlib
398 for v in self.to_list(y.uselib):
399 if not env['STATICLIB_' + v]:
400 if not v in self.uselib:
401 self.uselib.insert(0, v)
403 # if the library task generator provides 'export_incdirs', add to the include path
404 # the export_incdirs must be a list of paths relative to the other library
405 if getattr(y, 'export_incdirs', None):
406 for x in self.to_list(y.export_incdirs):
407 node = y.path.find_dir(x)
409 raise Utils.WafError('object %r: invalid folder %r in export_incdirs' % (y.target, x))
410 self.env.append_unique('INC_PATHS', node)
412 # 2. the case of the libs defined outside
413 for x in self.uselib:
414 for v in self.p_flag_vars:
415 val = self.env[v + '_' + x]
416 if val: self.env.append_value(v, val)
418 @feature('cprogram', 'cstaticlib', 'cshlib')
419 @after('init_cc', 'init_cxx', 'apply_link')
420 def apply_objdeps(self):
421 "add the .o files produced by some other object files in the same manner as uselib_local"
422 if not getattr(self, 'add_objects', None): return
425 names = self.to_list(self.add_objects)
429 # visit dependencies only once
434 # object does not exist ?
435 y = self.name_to_obj(x)
437 raise Utils.WafError('object %r was not found in uselib_local (required by add_objects %r)' % (x, self.name))
439 # object has ancestors to process first ? update the list of names
440 if getattr(y, 'add_objects', None):
442 lst = y.to_list(y.add_objects)
445 if u in seen: continue
448 if added: continue # list of names modified, loop
450 # safe to process the current object
454 for t in y.compiled_tasks:
455 self.link_task.inputs.extend(t.outputs)
457 @feature('cprogram', 'cshlib', 'cstaticlib')
458 @after('apply_lib_vars')
459 def apply_obj_vars(self):
460 """after apply_lib_vars for uselib"""
463 staticlib_st = v['STATICLIB_ST']
464 libpath_st = v['LIBPATH_ST']
465 staticlibpath_st = v['STATICLIBPATH_ST']
466 rpath_st = v['RPATH_ST']
468 app = v.append_unique
471 v.append_value('LINKFLAGS', v['FULLSTATIC_MARKER'])
475 app('LINKFLAGS', rpath_st % i)
477 for i in v['LIBPATH']:
478 app('LINKFLAGS', libpath_st % i)
479 app('LINKFLAGS', staticlibpath_st % i)
482 v.append_value('LINKFLAGS', v['STATICLIB_MARKER'])
483 k = [(staticlib_st % i) for i in v['STATICLIB']]
486 # fully static binaries ?
487 if not v['FULLSTATIC']:
488 if v['STATICLIB'] or v['LIB']:
489 v.append_value('LINKFLAGS', v['SHLIB_MARKER'])
491 app('LINKFLAGS', [lib_st % i for i in v['LIB']])
494 def process_obj_files(self):
495 if not hasattr(self, 'obj_files'): return
496 for x in self.obj_files:
497 node = self.path.find_resource(x)
498 self.link_task.inputs.append(node)
501 def add_obj_file(self, file):
502 """Small example on how to link object files as if they were source
503 obj = bld.create_obj('cc')
504 obj.add_obj_file('foo.o')"""
505 if not hasattr(self, 'obj_files'): self.obj_files = []
506 if not 'process_obj_files' in self.meths: self.meths.append('process_obj_files')
507 self.obj_files.append(file)
510 'cxxflag' : 'CXXFLAGS',
512 'ccflag' : 'CCFLAGS',
513 'linkflag' : 'LINKFLAGS',
514 'ldflag' : 'LINKFLAGS',
516 'libpath' : 'LIBPATH',
517 'staticlib': 'STATICLIB',
518 'staticlibpath': 'STATICLIBPATH',
520 'framework' : 'FRAMEWORK',
521 'frameworkpath' : 'FRAMEWORKPATH'
524 @feature('cc', 'cxx')
525 @before('init_cxx', 'init_cc')
526 @before('apply_lib_vars', 'apply_obj_vars', 'apply_incpaths', 'init_cc')
527 def add_extra_flags(self):
528 """case and plural insensitive
529 before apply_obj_vars for processing the library attributes
531 for x in self.__dict__.keys():
535 if c_attrs.get(y, None):
536 self.env.append_unique(c_attrs[y], getattr(self, x))
538 # ============ the code above must not know anything about import libs ==========
541 @after('apply_link', 'default_cc')
542 @before('apply_lib_vars', 'apply_objdeps', 'default_link_install')
543 def apply_implib(self):
544 """On mswindows, handle dlls and their import libs
545 the .dll.a is the import lib and it is required for linking so it is installed too
547 if not self.env.DEST_BINFMT == 'pe':
550 self.meths.remove('default_link_install')
552 bindir = self.install_path
553 if not bindir: return
555 # install the dll in the bin dir
556 dll = self.link_task.outputs[0]
557 self.bld.install_files(bindir, dll, self.env, self.chmod)
559 # add linker flags to generate the import lib
560 implib = self.env['implib_PATTERN'] % os.path.split(self.target)[1]
562 implib = dll.parent.find_or_declare(implib)
563 self.link_task.outputs.append(implib)
564 self.bld.install_as('${LIBDIR}/%s' % implib.name, implib, self.env)
566 self.env.append_value('LINKFLAGS', (self.env['IMPLIB_ST'] % implib.bldpath(self.env)).split())
568 # ============ the code above must not know anything about vnum processing on unix platforms =========
572 @before('apply_lib_vars', 'default_link_install')
573 def apply_vnum(self):
575 libfoo.so is installed as libfoo.so.1.2.3
577 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 self.meths.remove('default_link_install')
582 link = self.link_task
583 nums = self.vnum.split('.')
584 node = link.outputs[0]
587 if libname.endswith('.dylib'):
588 name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum)
589 name2 = libname.replace('.dylib', '.%s.dylib' % nums[0])
591 name3 = libname + '.' + self.vnum
592 name2 = libname + '.' + nums[0]
594 if self.env.SONAME_ST:
595 v = self.env.SONAME_ST % name2
596 self.env.append_value('LINKFLAGS', v.split())
599 nums = self.vnum.split('.')
601 path = self.install_path
604 if self.env.DEST_OS == 'openbsd':
605 libname = self.link_task.outputs[0].name
606 bld.install_as('%s%s%s' % (path, os.sep, libname), node, env=self.env)
608 bld.install_as(path + os.sep + name3, node, env=self.env)
609 bld.symlink_as(path + os.sep + name2, name3)
610 bld.symlink_as(path + os.sep + libname, name3)
612 # the following task is just to enable execution from the build dir :-/
613 if self.env.DEST_OS != 'openbsd':
614 self.create_task('vnum', node, [node.parent.find_or_declare(name2), node.parent.find_or_declare(name3)])
616 def exec_vnum_link(self):
617 for x in self.outputs:
618 path = x.abspath(self.env)
625 os.symlink(self.inputs[0].name, path)
629 cls = Task.task_type_from_func('vnum', func=exec_vnum_link, ext_in='.bin', color='CYAN')
632 # ============ the --as-needed flag should added during the configuration, not at runtime =========
635 def add_as_needed(conf):
636 if conf.env.DEST_BINFMT == 'elf' and 'gcc' in (conf.env.CXX_NAME, conf.env.CC_NAME):
637 conf.env.append_unique('LINKFLAGS', '--as-needed')