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 is_standard_libpath(env, path):
24 for _path in env.STANDARD_LIBPATH:
25 if _path == os.path.normpath(path):
29 def get_cc_version(conf, cc, gcc=False, icc=False):
31 cmd = cc + ['-dM', '-E', '-']
33 p = Utils.pproc.Popen(cmd, stdin=Utils.pproc.PIPE, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE)
35 out = p.communicate()[0]
37 conf.fatal('could not determine the compiler version %r' % cmd)
43 if out.find('__INTEL_COMPILER') >= 0:
44 conf.fatal('The intel compiler pretends to be gcc')
45 if out.find('__GNUC__') < 0:
46 conf.fatal('Could not determine the compiler type')
48 if icc and out.find('__INTEL_COMPILER') < 0:
49 conf.fatal('Not icc/icpc')
57 lst = shlex.split(line)
67 return var in k and k[var] != '0'
69 # Some documentation is available at http://predef.sourceforge.net
70 # The names given to DEST_OS must match what Utils.unversioned_sys_platform() returns.
72 '__linux__' : 'linux',
74 '__FreeBSD__' : 'freebsd',
75 '__NetBSD__' : 'netbsd',
76 '__OpenBSD__' : 'openbsd',
81 '__CYGWIN__' : 'cygwin',
86 '__POWERPC__' : 'powerpc',
91 conf.env.DEST_OS = mp1[i]
94 if isD('__APPLE__') and isD('__MACH__'):
95 conf.env.DEST_OS = 'darwin'
96 elif isD('__unix__'): # unix must be tested last as it's a generic fallback
97 conf.env.DEST_OS = 'generic'
100 conf.env.DEST_BINFMT = 'elf'
101 elif isD('__WINNT__') or isD('__CYGWIN__'):
102 conf.env.DEST_BINFMT = 'pe'
103 elif isD('__APPLE__'):
104 conf.env.DEST_BINFMT = 'mac-o'
107 '__x86_64__' : 'x86_64',
111 '__sparc__' : 'sparc',
112 '__alpha__' : 'alpha',
115 '__powerpc__' : 'powerpc',
119 conf.env.DEST_CPU = mp2[i]
122 debug('ccroot: dest platform: ' + ' '.join([conf.env[x] or '?' for x in ('DEST_OS', 'DEST_BINFMT', 'DEST_CPU')]))
123 conf.env['CC_VERSION'] = (k['__GNUC__'], k['__GNUC_MINOR__'], k['__GNUC_PATCHLEVEL__'])
127 """Will disappear in waf 1.6"""
128 ULTRADEBUG = "ultradebug"
131 OPTIMIZED = "optimized"
134 ALL = [ULTRADEBUG, DEBUG, RELEASE, OPTIMIZED, CUSTOM]
137 "look for .h the .cpp need"
138 debug('ccroot: _scan_preprocessor(self, node, env, path_lst)')
140 # TODO waf 1.6 - assume the default input has exactly one file
142 if len(self.inputs) == 1:
143 node = self.inputs[0]
144 (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS'])
146 debug('deps: deps for %s: %r; unresolved %r', str(node), nodes, names)
147 return (nodes, names)
152 for node in self.inputs:
153 (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS'])
155 debug('deps: deps for %s: %r; unresolved %r', str(node), nodes, names)
157 if id(x) in seen: continue
161 if not x in all_names:
163 return (all_nodes, all_names)
165 class ccroot_abstract(TaskGen.task_gen):
166 "Parent class for programs and libraries in languages c, c++ and moc (Qt)"
167 def __init__(self, *k, **kw):
168 # COMPAT remove in waf 1.6 TODO
173 TaskGen.task_gen.__init__(self, *k, **kw)
175 def get_target_name(self):
177 for x in self.features:
178 if x in ['cshlib', 'cstaticlib']:
181 pattern = self.env[tp + '_PATTERN']
182 if not pattern: pattern = '%s'
184 dir, name = os.path.split(self.target)
186 if self.env.DEST_BINFMT == 'pe' and getattr(self, 'vnum', None) and 'cshlib' in self.features:
187 # include the version in the dll file name,
188 # the import lib file name stays unversionned.
189 name = name + '-' + self.vnum.split('.')[0]
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,
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)
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')
232 @before('apply_core')
233 def vars_target_cprogram(self):
234 self.default_install_path = self.env.BINDIR
235 self.default_chmod = O755
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
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
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]:
269 if preproc.go_absolute:
270 for path in preproc.standard_includes:
274 for path in self.to_list(self.includes):
276 if preproc.go_absolute or not os.path.isabs(path):
279 self.env.prepend_value('CPPPATH', path)
283 if os.path.isabs(path):
284 if preproc.go_absolute:
285 node = self.bld.root.find_dir(path)
287 node = self.bld.srcnode
289 node = node.find_dir(path[1:])
291 node = self.path.find_dir(path)
294 self.env.append_value('INC_PATHS', node)
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']:
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)
321 value = self.env[compvar]
322 if value: self.env.append_value(var, value)
324 @feature('cprogram', 'cshlib', 'cstaticlib')
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)
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)))
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
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)
358 tmp = Utils.deque(names) # consume a copy of the list of names
360 lib_name = tmp.popleft()
361 # visit dependencies only once
365 y = self.name_to_obj(lib_name)
367 raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.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]
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)
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)
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
427 names = self.to_list(self.add_objects)
431 # visit dependencies only once
436 # object does not exist ?
437 y = self.name_to_obj(x)
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):
444 lst = y.to_list(y.add_objects)
447 if u in seen: continue
450 if added: continue # list of names modified, loop
452 # safe to process the current object
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"""
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
473 v.append_value('LINKFLAGS', v['FULLSTATIC_MARKER'])
476 if is_standard_libpath(v, i):
479 app('LINKFLAGS', rpath_st % i)
481 for i in v['LIBPATH']:
482 if is_standard_libpath(v, i):
484 app('LINKFLAGS', libpath_st % i)
485 app('LINKFLAGS', staticlibpath_st % i)
488 v.append_value('LINKFLAGS', v['STATICLIB_MARKER'])
489 k = [(staticlib_st % i) for i in v['STATICLIB']]
492 # fully static binaries ?
493 if not v['FULLSTATIC']:
494 if v['STATICLIB'] or v['LIB']:
495 v.append_value('LINKFLAGS', v['SHLIB_MARKER'])
497 app('LINKFLAGS', [lib_st % i for i in v['LIB']])
500 def process_obj_files(self):
501 if not hasattr(self, 'obj_files'): return
502 for x in self.obj_files:
503 node = self.path.find_resource(x)
504 self.link_task.inputs.append(node)
507 def add_obj_file(self, file):
508 """Small example on how to link object files as if they were source
509 obj = bld.create_obj('cc')
510 obj.add_obj_file('foo.o')"""
511 if not hasattr(self, 'obj_files'): self.obj_files = []
512 if not 'process_obj_files' in self.meths: self.meths.append('process_obj_files')
513 self.obj_files.append(file)
516 'cxxflag' : 'CXXFLAGS',
518 'ccflag' : 'CCFLAGS',
519 'linkflag' : 'LINKFLAGS',
520 'ldflag' : 'LINKFLAGS',
522 'libpath' : 'LIBPATH',
523 'staticlib': 'STATICLIB',
524 'staticlibpath': 'STATICLIBPATH',
526 'framework' : 'FRAMEWORK',
527 'frameworkpath' : 'FRAMEWORKPATH'
530 @feature('cc', 'cxx')
531 @before('init_cxx', 'init_cc')
532 @before('apply_lib_vars', 'apply_obj_vars', 'apply_incpaths', 'init_cc')
533 def add_extra_flags(self):
534 """case and plural insensitive
535 before apply_obj_vars for processing the library attributes
537 for x in self.__dict__.keys():
541 if c_attrs.get(y, None):
542 self.env.append_unique(c_attrs[y], getattr(self, x))
544 # ============ the code above must not know anything about import libs ==========
547 @after('apply_link', 'default_cc')
548 @before('apply_lib_vars', 'apply_objdeps', 'default_link_install')
549 def apply_implib(self):
550 """On mswindows, handle dlls and their import libs
551 the .dll.a is the import lib and it is required for linking so it is installed too
553 if not self.env.DEST_BINFMT == 'pe':
556 self.meths.remove('default_link_install')
558 bindir = self.install_path
559 if not bindir: return
561 # install the dll in the bin dir
562 dll = self.link_task.outputs[0]
563 self.bld.install_files(bindir, dll, self.env, self.chmod)
565 # add linker flags to generate the import lib
566 implib = self.env['implib_PATTERN'] % os.path.split(self.target)[1]
568 implib = dll.parent.find_or_declare(implib)
569 self.link_task.outputs.append(implib)
570 self.bld.install_as('${LIBDIR}/%s' % implib.name, implib, self.env)
572 self.env.append_value('LINKFLAGS', (self.env['IMPLIB_ST'] % implib.bldpath(self.env)).split())
574 # ============ the code above must not know anything about vnum processing on unix platforms =========
578 @before('apply_lib_vars', 'default_link_install')
579 def apply_vnum(self):
581 libfoo.so is installed as libfoo.so.1.2.3
583 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'):
586 self.meths.remove('default_link_install')
588 link = self.link_task
589 nums = self.vnum.split('.')
590 node = link.outputs[0]
593 if libname.endswith('.dylib'):
594 name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum)
595 name2 = libname.replace('.dylib', '.%s.dylib' % nums[0])
597 name3 = libname + '.' + self.vnum
598 name2 = libname + '.' + nums[0]
600 if self.env.SONAME_ST:
601 v = self.env.SONAME_ST % name2
602 self.env.append_value('LINKFLAGS', v.split())
605 nums = self.vnum.split('.')
607 path = self.install_path
610 if self.env.DEST_OS == 'openbsd':
611 bld.install_as(path + os.sep + name2, node, env=self.env, chmod=self.link_task.chmod)
613 bld.install_as(path + os.sep + name3, node, env=self.env)
614 bld.symlink_as(path + os.sep + name2, name3)
615 bld.symlink_as(path + os.sep + libname, name3)
617 # the following task is just to enable execution from the build dir :-/
618 self.create_task('vnum', node, [node.parent.find_or_declare(name2), node.parent.find_or_declare(name3)])
620 def exec_vnum_link(self):
621 for x in self.outputs:
622 path = x.abspath(self.env)
629 os.symlink(self.inputs[0].name, path)
633 cls = Task.task_type_from_func('vnum', func=exec_vnum_link, ext_in='.bin', color='CYAN')
636 # ============ the --as-needed flag should added during the configuration, not at runtime =========
639 def add_as_needed(conf):
640 if conf.env.DEST_BINFMT == 'elf' and 'gcc' in (conf.env.CXX_NAME, conf.env.CC_NAME):
641 conf.env.append_unique('LINKFLAGS', '--as-needed')