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' % (pattern, nums[0])
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,
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'])
477 app('LINKFLAGS', rpath_st % i)
479 for i in v['LIBPATH']:
480 app('LINKFLAGS', libpath_st % i)
481 app('LINKFLAGS', staticlibpath_st % i)
484 v.append_value('LINKFLAGS', v['STATICLIB_MARKER'])
485 k = [(staticlib_st % i) for i in v['STATICLIB']]
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']])
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)
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)
512 'cxxflag' : 'CXXFLAGS',
514 'ccflag' : 'CCFLAGS',
515 'linkflag' : 'LINKFLAGS',
516 'ldflag' : 'LINKFLAGS',
518 'libpath' : 'LIBPATH',
519 'staticlib': 'STATICLIB',
520 'staticlibpath': 'STATICLIBPATH',
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():
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 ==========
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':
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 =========
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'):
582 self.meths.remove('default_link_install')
584 link = self.link_task
585 nums = self.vnum.split('.')
586 node = link.outputs[0]
589 if libname.endswith('.dylib'):
590 name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum)
591 name2 = libname.replace('.dylib', '.%s.dylib' % nums[0])
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())
601 nums = self.vnum.split('.')
603 path = self.install_path
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)
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)
627 os.symlink(self.inputs[0].name, path)
631 cls = Task.task_type_from_func('vnum', func=exec_vnum_link, ext_in='.bin', color='CYAN')
634 # ============ the --as-needed flag should added during the configuration, not at runtime =========
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')