1#!/usr/bin/env python
2# encoding: utf-8
3#
4# partially based on boost.py written by Gernot Vormayr
5# written by Ruediger Sonderfeld <ruediger@c-plusplus.de>, 2008
6# modified by Bjoern Michaelsen, 2008
7# modified by Luca Fossati, 2008
8# rewritten for waf 1.5.1, Thomas Nagy, 2008
9# rewritten for waf 1.6.2, Sylvain Rouquette, 2011
10
11'''
12
13This is an extra tool, not bundled with the default waf binary.
14To add the boost tool to the waf file:
15$ ./waf-light --tools=compat15,boost
16	or, if you have waf >= 1.6.2
17$ ./waf update --files=boost
18
19When using this tool, the wscript will look like:
20
21	def options(opt):
22		opt.load('compiler_cxx boost')
23
24	def configure(conf):
25		conf.load('compiler_cxx boost')
26		conf.check_boost(lib='system filesystem')
27
28	def build(bld):
29		bld(source='main.cpp', target='app', use='BOOST')
30
31Options are generated, in order to specify the location of boost includes/libraries.
32The `check_boost` configuration function allows to specify the used boost libraries.
33It can also provide default arguments to the --boost-mt command-line arguments.
34Everything will be packaged together in a BOOST component that you can use.
35
36When using MSVC, a lot of compilation flags need to match your BOOST build configuration:
37 - you may have to add /EHsc to your CXXFLAGS or define boost::throw_exception if BOOST_NO_EXCEPTIONS is defined.
38   Errors: C4530
39 - boost libraries will try to be smart and use the (pretty but often not useful) auto-linking feature of MSVC
40   So before calling `conf.check_boost` you might want to disabling by adding
41		conf.env.DEFINES_BOOST += ['BOOST_ALL_NO_LIB']
42   Errors:
43 - boost might also be compiled with /MT, which links the runtime statically.
44   If you have problems with redefined symbols,
45		self.env['DEFINES_%s' % var] += ['BOOST_ALL_NO_LIB']
46		self.env['CXXFLAGS_%s' % var] += ['/MD', '/EHsc']
47Passing `--boost-linkage_autodetect` might help ensuring having a correct linkage in some basic cases.
48
49'''
50
51import sys
52import re
53from waflib import Utils, Logs, Errors
54from waflib.Configure import conf
55from waflib.TaskGen import feature, after_method
56
57BOOST_LIBS = ['/usr/lib', '/usr/local/lib', '/opt/local/lib', '/sw/lib', '/lib']
58BOOST_INCLUDES = ['/usr/include', '/usr/local/include', '/opt/local/include', '/sw/include']
59BOOST_VERSION_FILE = 'boost/version.hpp'
60BOOST_VERSION_CODE = '''
61#include <iostream>
62#include <boost/version.hpp>
63int main() { std::cout << BOOST_LIB_VERSION << ":" << BOOST_VERSION << std::endl; }
64'''
65
66BOOST_ERROR_CODE = '''
67#include <boost/system/error_code.hpp>
68int main() { boost::system::error_code c; }
69'''
70
71PTHREAD_CODE = '''
72#include <pthread.h>
73static void* f(void*) { return 0; }
74int main() {
75	pthread_t th;
76	pthread_attr_t attr;
77	pthread_attr_init(&attr);
78	pthread_create(&th, &attr, &f, 0);
79	pthread_join(th, 0);
80	pthread_cleanup_push(0, 0);
81	pthread_cleanup_pop(0);
82	pthread_attr_destroy(&attr);
83}
84'''
85
86BOOST_THREAD_CODE = '''
87#include <boost/thread.hpp>
88int main() { boost::thread t; }
89'''
90
91BOOST_LOG_CODE = '''
92#include <boost/log/trivial.hpp>
93#include <boost/log/utility/setup/console.hpp>
94#include <boost/log/utility/setup/common_attributes.hpp>
95int main() {
96	using namespace boost::log;
97	add_common_attributes();
98	add_console_log(std::clog, keywords::format = "%Message%");
99	BOOST_LOG_TRIVIAL(debug) << "log is working" << std::endl;
100}
101'''
102
103# toolsets from {boost_dir}/tools/build/v2/tools/common.jam
104PLATFORM = Utils.unversioned_sys_platform()
105detect_intel = lambda env: (PLATFORM == 'win32') and 'iw' or 'il'
106detect_clang = lambda env: (PLATFORM == 'darwin') and 'clang-darwin' or 'clang'
107detect_mingw = lambda env: (re.search('MinGW', env.CXX[0])) and 'mgw' or 'gcc'
108BOOST_TOOLSETS = {
109	'borland':  'bcb',
110	'clang':	detect_clang,
111	'como':	 'como',
112	'cw':	   'cw',
113	'darwin':   'xgcc',
114	'edg':	  'edg',
115	'g++':	  detect_mingw,
116	'gcc':	  detect_mingw,
117	'icpc':	 detect_intel,
118	'intel':	detect_intel,
119	'kcc':	  'kcc',
120	'kylix':	'bck',
121	'mipspro':  'mp',
122	'mingw':	'mgw',
123	'msvc':	 'vc',
124	'qcc':	  'qcc',
125	'sun':	  'sw',
126	'sunc++':   'sw',
127	'tru64cxx': 'tru',
128	'vacpp':	'xlc'
129}
130
131
132def options(opt):
133	opt = opt.add_option_group('Boost Options')
134	opt.add_option('--boost-includes', type='string',
135				   default='', dest='boost_includes',
136				   help='''path to the directory where the boost includes are,
137				   e.g., /path/to/boost_1_55_0/stage/include''')
138	opt.add_option('--boost-libs', type='string',
139				   default='', dest='boost_libs',
140				   help='''path to the directory where the boost libs are,
141				   e.g., path/to/boost_1_55_0/stage/lib''')
142	opt.add_option('--boost-mt', action='store_true',
143				   default=False, dest='boost_mt',
144				   help='select multi-threaded libraries')
145	opt.add_option('--boost-abi', type='string', default='', dest='boost_abi',
146				   help='''select libraries with tags (gd for debug, static is automatically added),
147				   see doc Boost, Getting Started, chapter 6.1''')
148	opt.add_option('--boost-linkage_autodetect', action="store_true", dest='boost_linkage_autodetect',
149				   help="auto-detect boost linkage options (don't get used to it / might break other stuff)")
150	opt.add_option('--boost-toolset', type='string',
151				   default='', dest='boost_toolset',
152				   help='force a toolset e.g. msvc, vc90, \
153						gcc, mingw, mgw45 (default: auto)')
154	py_version = '%d%d' % (sys.version_info[0], sys.version_info[1])
155	opt.add_option('--boost-python', type='string',
156				   default=py_version, dest='boost_python',
157				   help='select the lib python with this version \
158						(default: %s)' % py_version)
159
160
161@conf
162def __boost_get_version_file(self, d):
163	if not d:
164		return None
165	dnode = self.root.find_dir(d)
166	if dnode:
167		return dnode.find_node(BOOST_VERSION_FILE)
168	return None
169
170@conf
171def boost_get_version(self, d):
172	"""silently retrieve the boost version number"""
173	node = self.__boost_get_version_file(d)
174	if node:
175		try:
176			txt = node.read()
177		except EnvironmentError:
178			Logs.error("Could not read the file %r", node.abspath())
179		else:
180			re_but1 = re.compile('^#define\\s+BOOST_LIB_VERSION\\s+"(.+)"', re.M)
181			m1 = re_but1.search(txt)
182			re_but2 = re.compile('^#define\\s+BOOST_VERSION\\s+(\\d+)', re.M)
183			m2 = re_but2.search(txt)
184			if m1 and m2:
185				return (m1.group(1), m2.group(1))
186	return self.check_cxx(fragment=BOOST_VERSION_CODE, includes=[d], execute=True, define_ret=True).split(":")
187
188@conf
189def boost_get_includes(self, *k, **kw):
190	includes = k and k[0] or kw.get('includes')
191	if includes and self.__boost_get_version_file(includes):
192		return includes
193	for d in self.environ.get('INCLUDE', '').split(';') + BOOST_INCLUDES:
194		if self.__boost_get_version_file(d):
195			return d
196	if includes:
197		self.end_msg('headers not found in %s' % includes)
198		self.fatal('The configuration failed')
199	else:
200		self.end_msg('headers not found, please provide a --boost-includes argument (see help)')
201		self.fatal('The configuration failed')
202
203
204@conf
205def boost_get_toolset(self, cc):
206	toolset = cc
207	if not cc:
208		build_platform = Utils.unversioned_sys_platform()
209		if build_platform in BOOST_TOOLSETS:
210			cc = build_platform
211		else:
212			cc = self.env.CXX_NAME
213	if cc in BOOST_TOOLSETS:
214		toolset = BOOST_TOOLSETS[cc]
215	return isinstance(toolset, str) and toolset or toolset(self.env)
216
217
218@conf
219def __boost_get_libs_path(self, *k, **kw):
220	''' return the lib path and all the files in it '''
221	if 'files' in kw:
222		return self.root.find_dir('.'), Utils.to_list(kw['files'])
223	libs = k and k[0] or kw.get('libs')
224	if libs:
225		path = self.root.find_dir(libs)
226		files = path.ant_glob('*boost_*')
227	if not libs or not files:
228		for d in self.environ.get('LIB', '').split(';') + BOOST_LIBS:
229			if not d:
230				continue
231			path = self.root.find_dir(d)
232			if path:
233				files = path.ant_glob('*boost_*')
234				if files:
235					break
236			path = self.root.find_dir(d + '64')
237			if path:
238				files = path.ant_glob('*boost_*')
239				if files:
240					break
241	if not path:
242		if libs:
243			self.end_msg('libs not found in %s' % libs)
244			self.fatal('The configuration failed')
245		else:
246			self.end_msg('libs not found, please provide a --boost-libs argument (see help)')
247			self.fatal('The configuration failed')
248
249	self.to_log('Found the boost path in %r with the libraries:' % path)
250	for x in files:
251		self.to_log('    %r' % x)
252	return path, files
253
254@conf
255def boost_get_libs(self, *k, **kw):
256	'''
257	return the lib path and the required libs
258	according to the parameters
259	'''
260	path, files = self.__boost_get_libs_path(**kw)
261	files = sorted(files, key=lambda f: (len(f.name), f.name), reverse=True)
262	toolset = self.boost_get_toolset(kw.get('toolset', ''))
263	toolset_pat = '(-%s[0-9]{0,3})' % toolset
264	version = '-%s' % self.env.BOOST_VERSION
265
266	def find_lib(re_lib, files):
267		for file in files:
268			if re_lib.search(file.name):
269				self.to_log('Found boost lib %s' % file)
270				return file
271		return None
272
273	def format_lib_name(name):
274		if name.startswith('lib') and self.env.CC_NAME != 'msvc':
275			name = name[3:]
276		return name[:name.rfind('.')]
277
278	def match_libs(lib_names, is_static):
279		libs = []
280		lib_names = Utils.to_list(lib_names)
281		if not lib_names:
282			return libs
283		t = []
284		if kw.get('mt', False):
285			t.append('-mt')
286		if kw.get('abi'):
287			t.append('%s%s' % (is_static and '-s' or '-', kw['abi']))
288		elif is_static:
289			t.append('-s')
290		tags_pat = t and ''.join(t) or ''
291		ext = is_static and self.env.cxxstlib_PATTERN or self.env.cxxshlib_PATTERN
292		ext = ext.partition('%s')[2] # remove '%s' or 'lib%s' from PATTERN
293
294		for lib in lib_names:
295			if lib == 'python':
296				# for instance, with python='27',
297				# accepts '-py27', '-py2', '27', '-2.7' and '2'
298				# but will reject '-py3', '-py26', '26' and '3'
299				tags = '({0})?((-py{2})|(-py{1}(?=[^0-9]))|({2})|(-{1}.{3})|({1}(?=[^0-9]))|(?=[^0-9])(?!-py))'.format(tags_pat, kw['python'][0], kw['python'], kw['python'][1])
300			else:
301				tags = tags_pat
302			# Trying libraries, from most strict match to least one
303			for pattern in ['boost_%s%s%s%s%s$' % (lib, toolset_pat, tags, version, ext),
304							'boost_%s%s%s%s$' % (lib, tags, version, ext),
305							# Give up trying to find the right version
306							'boost_%s%s%s%s$' % (lib, toolset_pat, tags, ext),
307							'boost_%s%s%s$' % (lib, tags, ext),
308							'boost_%s%s$' % (lib, ext),
309							'boost_%s' % lib]:
310				self.to_log('Trying pattern %s' % pattern)
311				file = find_lib(re.compile(pattern), files)
312				if file:
313					libs.append(format_lib_name(file.name))
314					break
315			else:
316				self.end_msg('lib %s not found in %s' % (lib, path.abspath()))
317				self.fatal('The configuration failed')
318		return libs
319
320	return  path.abspath(), match_libs(kw.get('lib'), False), match_libs(kw.get('stlib'), True)
321
322@conf
323def _check_pthread_flag(self, *k, **kw):
324	'''
325	Computes which flags should be added to CXXFLAGS and LINKFLAGS to compile in multi-threading mode
326
327	Yes, we *need* to put the -pthread thing in CPPFLAGS because with GCC3,
328	boost/thread.hpp will trigger a #error if -pthread isn't used:
329	  boost/config/requires_threads.hpp:47:5: #error "Compiler threading support
330	  is not turned on. Please set the correct command line options for
331	  threading: -pthread (Linux), -pthreads (Solaris) or -mthreads (Mingw32)"
332
333	Based on _BOOST_PTHREAD_FLAG(): https://github.com/tsuna/boost.m4/blob/master/build-aux/boost.m4
334    '''
335
336	var = kw.get('uselib_store', 'BOOST')
337
338	self.start_msg('Checking the flags needed to use pthreads')
339
340	# The ordering *is* (sometimes) important.  Some notes on the
341	# individual items follow:
342	# (none): in case threads are in libc; should be tried before -Kthread and
343	#       other compiler flags to prevent continual compiler warnings
344	# -lpthreads: AIX (must check this before -lpthread)
345	# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
346	# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
347	# -llthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
348	# -pthread: GNU Linux/GCC (kernel threads), BSD/GCC (userland threads)
349	# -pthreads: Solaris/GCC
350	# -mthreads: MinGW32/GCC, Lynx/GCC
351	# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
352	#      doesn't hurt to check since this sometimes defines pthreads too;
353	#      also defines -D_REENTRANT)
354	#      ... -mt is also the pthreads flag for HP/aCC
355	# -lpthread: GNU Linux, etc.
356	# --thread-safe: KAI C++
357	if Utils.unversioned_sys_platform() == "sunos":
358		# On Solaris (at least, for some versions), libc contains stubbed
359		# (non-functional) versions of the pthreads routines, so link-based
360		# tests will erroneously succeed.  (We need to link with -pthreads/-mt/
361		# -lpthread.)  (The stubs are missing pthread_cleanup_push, or rather
362		# a function called by this macro, so we could check for that, but
363		# who knows whether they'll stub that too in a future libc.)  So,
364		# we'll just look for -pthreads and -lpthread first:
365		boost_pthread_flags = ["-pthreads", "-lpthread", "-mt", "-pthread"]
366	else:
367		boost_pthread_flags = ["", "-lpthreads", "-Kthread", "-kthread", "-llthread", "-pthread",
368							   "-pthreads", "-mthreads", "-lpthread", "--thread-safe", "-mt"]
369
370	for boost_pthread_flag in boost_pthread_flags:
371		try:
372			self.env.stash()
373			self.env.append_value('CXXFLAGS_%s' % var, boost_pthread_flag)
374			self.env.append_value('LINKFLAGS_%s' % var, boost_pthread_flag)
375			self.check_cxx(code=PTHREAD_CODE, msg=None, use=var, execute=False)
376
377			self.end_msg(boost_pthread_flag)
378			return
379		except self.errors.ConfigurationError:
380			self.env.revert()
381	self.end_msg('None')
382
383@conf
384def check_boost(self, *k, **kw):
385	"""
386	Initialize boost libraries to be used.
387
388	Keywords: you can pass the same parameters as with the command line (without "--boost-").
389	Note that the command line has the priority, and should preferably be used.
390	"""
391	if not self.env['CXX']:
392		self.fatal('load a c++ compiler first, conf.load("compiler_cxx")')
393
394	params = {
395		'lib': k and k[0] or kw.get('lib'),
396		'stlib': kw.get('stlib')
397	}
398	for key, value in self.options.__dict__.items():
399		if not key.startswith('boost_'):
400			continue
401		key = key[len('boost_'):]
402		params[key] = value and value or kw.get(key, '')
403
404	var = kw.get('uselib_store', 'BOOST')
405
406	self.find_program('dpkg-architecture', var='DPKG_ARCHITECTURE', mandatory=False)
407	if self.env.DPKG_ARCHITECTURE:
408		deb_host_multiarch = self.cmd_and_log([self.env.DPKG_ARCHITECTURE[0], '-qDEB_HOST_MULTIARCH'])
409		BOOST_LIBS.insert(0, '/usr/lib/%s' % deb_host_multiarch.strip())
410
411	self.start_msg('Checking boost includes')
412	self.env['INCLUDES_%s' % var] = inc = self.boost_get_includes(**params)
413	versions = self.boost_get_version(inc)
414	self.env.BOOST_VERSION = versions[0]
415	self.env.BOOST_VERSION_NUMBER = int(versions[1])
416	self.end_msg("%d.%d.%d" % (int(versions[1]) / 100000,
417							   int(versions[1]) / 100 % 1000,
418							   int(versions[1]) % 100))
419	if Logs.verbose:
420		Logs.pprint('CYAN', '	path : %s' % self.env['INCLUDES_%s' % var])
421
422	if not params['lib'] and not params['stlib']:
423		return
424	if 'static' in kw or 'static' in params:
425		Logs.warn('boost: static parameter is deprecated, use stlib instead.')
426	self.start_msg('Checking boost libs')
427	path, libs, stlibs = self.boost_get_libs(**params)
428	self.env['LIBPATH_%s' % var] = [path]
429	self.env['STLIBPATH_%s' % var] = [path]
430	self.env['LIB_%s' % var] = libs
431	self.env['STLIB_%s' % var] = stlibs
432	self.end_msg('ok')
433	if Logs.verbose:
434		Logs.pprint('CYAN', '	path : %s' % path)
435		Logs.pprint('CYAN', '	shared libs : %s' % libs)
436		Logs.pprint('CYAN', '	static libs : %s' % stlibs)
437
438	def has_shlib(lib):
439		return params['lib'] and lib in params['lib']
440	def has_stlib(lib):
441		return params['stlib'] and lib in params['stlib']
442	def has_lib(lib):
443		return has_shlib(lib) or has_stlib(lib)
444	if has_lib('thread'):
445		# not inside try_link to make check visible in the output
446		self._check_pthread_flag(k, kw)
447
448	def try_link():
449		if has_lib('system'):
450			self.check_cxx(fragment=BOOST_ERROR_CODE, use=var, execute=False)
451		if has_lib('thread'):
452			self.check_cxx(fragment=BOOST_THREAD_CODE, use=var, execute=False)
453		if has_lib('log'):
454			if not has_lib('thread'):
455				self.env['DEFINES_%s' % var] += ['BOOST_LOG_NO_THREADS']
456			if has_shlib('log'):
457				self.env['DEFINES_%s' % var] += ['BOOST_LOG_DYN_LINK']
458			self.check_cxx(fragment=BOOST_LOG_CODE, use=var, execute=False)
459
460	if params.get('linkage_autodetect', False):
461		self.start_msg("Attempting to detect boost linkage flags")
462		toolset = self.boost_get_toolset(kw.get('toolset', ''))
463		if toolset in ('vc',):
464			# disable auto-linking feature, causing error LNK1181
465			# because the code wants to be linked against
466			self.env['DEFINES_%s' % var] += ['BOOST_ALL_NO_LIB']
467
468			# if no dlls are present, we guess the .lib files are not stubs
469			has_dlls = False
470			for x in Utils.listdir(path):
471				if x.endswith(self.env.cxxshlib_PATTERN % ''):
472					has_dlls = True
473					break
474			if not has_dlls:
475				self.env['STLIBPATH_%s' % var] = [path]
476				self.env['STLIB_%s' % var] = libs
477				del self.env['LIB_%s' % var]
478				del self.env['LIBPATH_%s' % var]
479
480			# we attempt to play with some known-to-work CXXFLAGS combinations
481			for cxxflags in (['/MD', '/EHsc'], []):
482				self.env.stash()
483				self.env["CXXFLAGS_%s" % var] += cxxflags
484				try:
485					try_link()
486				except Errors.ConfigurationError as e:
487					self.env.revert()
488					exc = e
489				else:
490					self.end_msg("ok: winning cxxflags combination: %s" % (self.env["CXXFLAGS_%s" % var]))
491					exc = None
492					self.env.commit()
493					break
494
495			if exc is not None:
496				self.end_msg("Could not auto-detect boost linking flags combination, you may report it to boost.py author", ex=exc)
497				self.fatal('The configuration failed')
498		else:
499			self.end_msg("Boost linkage flags auto-detection not implemented (needed ?) for this toolchain")
500			self.fatal('The configuration failed')
501	else:
502		self.start_msg('Checking for boost linkage')
503		try:
504			try_link()
505		except Errors.ConfigurationError as e:
506			self.end_msg("Could not link against boost libraries using supplied options")
507			self.fatal('The configuration failed')
508		self.end_msg('ok')
509
510
511@feature('cxx')
512@after_method('apply_link')
513def install_boost(self):
514	if install_boost.done or not Utils.is_win32 or not self.bld.cmd.startswith('install'):
515		return
516	install_boost.done = True
517	inst_to = getattr(self, 'install_path', '${BINDIR}')
518	for lib in self.env.LIB_BOOST:
519		try:
520			file = self.bld.find_file(self.env.cxxshlib_PATTERN % lib, self.env.LIBPATH_BOOST)
521			self.add_install_files(install_to=inst_to, install_from=self.bld.root.find_node(file))
522		except:
523			continue
524install_boost.done = False
525
526