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	# extensions from Tools.ccroot.lib_patterns
274	wo_ext = re.compile(r"\.(a|so|lib|dll|dylib)(\.[0-9\.]+)?$")
275	def format_lib_name(name):
276		if name.startswith('lib') and self.env.CC_NAME != 'msvc':
277			name = name[3:]
278		return wo_ext.sub("", name)
279
280	def match_libs(lib_names, is_static):
281		libs = []
282		lib_names = Utils.to_list(lib_names)
283		if not lib_names:
284			return libs
285		t = []
286		if kw.get('mt', False):
287			t.append('-mt')
288		if kw.get('abi'):
289			t.append('%s%s' % (is_static and '-s' or '-', kw['abi']))
290		elif is_static:
291			t.append('-s')
292		tags_pat = t and ''.join(t) or ''
293		ext = is_static and self.env.cxxstlib_PATTERN or self.env.cxxshlib_PATTERN
294		ext = ext.partition('%s')[2] # remove '%s' or 'lib%s' from PATTERN
295
296		for lib in lib_names:
297			if lib == 'python':
298				# for instance, with python='27',
299				# accepts '-py27', '-py2', '27', '-2.7' and '2'
300				# but will reject '-py3', '-py26', '26' and '3'
301				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])
302			else:
303				tags = tags_pat
304			# Trying libraries, from most strict match to least one
305			for pattern in ['boost_%s%s%s%s%s$' % (lib, toolset_pat, tags, version, ext),
306							'boost_%s%s%s%s$' % (lib, tags, version, ext),
307							# Give up trying to find the right version
308							'boost_%s%s%s%s$' % (lib, toolset_pat, tags, ext),
309							'boost_%s%s%s$' % (lib, tags, ext),
310							'boost_%s%s$' % (lib, ext),
311							'boost_%s' % lib]:
312				self.to_log('Trying pattern %s' % pattern)
313				file = find_lib(re.compile(pattern), files)
314				if file:
315					libs.append(format_lib_name(file.name))
316					break
317			else:
318				self.end_msg('lib %s not found in %s' % (lib, path.abspath()))
319				self.fatal('The configuration failed')
320		return libs
321
322	return  path.abspath(), match_libs(kw.get('lib'), False), match_libs(kw.get('stlib'), True)
323
324@conf
325def _check_pthread_flag(self, *k, **kw):
326	'''
327	Computes which flags should be added to CXXFLAGS and LINKFLAGS to compile in multi-threading mode
328
329	Yes, we *need* to put the -pthread thing in CPPFLAGS because with GCC3,
330	boost/thread.hpp will trigger a #error if -pthread isn't used:
331	  boost/config/requires_threads.hpp:47:5: #error "Compiler threading support
332	  is not turned on. Please set the correct command line options for
333	  threading: -pthread (Linux), -pthreads (Solaris) or -mthreads (Mingw32)"
334
335	Based on _BOOST_PTHREAD_FLAG(): https://github.com/tsuna/boost.m4/blob/master/build-aux/boost.m4
336    '''
337
338	var = kw.get('uselib_store', 'BOOST')
339
340	self.start_msg('Checking the flags needed to use pthreads')
341
342	# The ordering *is* (sometimes) important.  Some notes on the
343	# individual items follow:
344	# (none): in case threads are in libc; should be tried before -Kthread and
345	#       other compiler flags to prevent continual compiler warnings
346	# -lpthreads: AIX (must check this before -lpthread)
347	# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
348	# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
349	# -llthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
350	# -pthread: GNU Linux/GCC (kernel threads), BSD/GCC (userland threads)
351	# -pthreads: Solaris/GCC
352	# -mthreads: MinGW32/GCC, Lynx/GCC
353	# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
354	#      doesn't hurt to check since this sometimes defines pthreads too;
355	#      also defines -D_REENTRANT)
356	#      ... -mt is also the pthreads flag for HP/aCC
357	# -lpthread: GNU Linux, etc.
358	# --thread-safe: KAI C++
359	if Utils.unversioned_sys_platform() == "sunos":
360		# On Solaris (at least, for some versions), libc contains stubbed
361		# (non-functional) versions of the pthreads routines, so link-based
362		# tests will erroneously succeed.  (We need to link with -pthreads/-mt/
363		# -lpthread.)  (The stubs are missing pthread_cleanup_push, or rather
364		# a function called by this macro, so we could check for that, but
365		# who knows whether they'll stub that too in a future libc.)  So,
366		# we'll just look for -pthreads and -lpthread first:
367		boost_pthread_flags = ["-pthreads", "-lpthread", "-mt", "-pthread"]
368	else:
369		boost_pthread_flags = ["", "-lpthreads", "-Kthread", "-kthread", "-llthread", "-pthread",
370							   "-pthreads", "-mthreads", "-lpthread", "--thread-safe", "-mt"]
371
372	for boost_pthread_flag in boost_pthread_flags:
373		try:
374			self.env.stash()
375			self.env.append_value('CXXFLAGS_%s' % var, boost_pthread_flag)
376			self.env.append_value('LINKFLAGS_%s' % var, boost_pthread_flag)
377			self.check_cxx(code=PTHREAD_CODE, msg=None, use=var, execute=False)
378
379			self.end_msg(boost_pthread_flag)
380			return
381		except self.errors.ConfigurationError:
382			self.env.revert()
383	self.end_msg('None')
384
385@conf
386def check_boost(self, *k, **kw):
387	"""
388	Initialize boost libraries to be used.
389
390	Keywords: you can pass the same parameters as with the command line (without "--boost-").
391	Note that the command line has the priority, and should preferably be used.
392	"""
393	if not self.env['CXX']:
394		self.fatal('load a c++ compiler first, conf.load("compiler_cxx")')
395
396	params = {
397		'lib': k and k[0] or kw.get('lib'),
398		'stlib': kw.get('stlib')
399	}
400	for key, value in self.options.__dict__.items():
401		if not key.startswith('boost_'):
402			continue
403		key = key[len('boost_'):]
404		params[key] = value and value or kw.get(key, '')
405
406	var = kw.get('uselib_store', 'BOOST')
407
408	self.find_program('dpkg-architecture', var='DPKG_ARCHITECTURE', mandatory=False)
409	if self.env.DPKG_ARCHITECTURE:
410		deb_host_multiarch = self.cmd_and_log([self.env.DPKG_ARCHITECTURE[0], '-qDEB_HOST_MULTIARCH'])
411		BOOST_LIBS.insert(0, '/usr/lib/%s' % deb_host_multiarch.strip())
412
413	self.start_msg('Checking boost includes')
414	self.env['INCLUDES_%s' % var] = inc = self.boost_get_includes(**params)
415	versions = self.boost_get_version(inc)
416	self.env.BOOST_VERSION = versions[0]
417	self.env.BOOST_VERSION_NUMBER = int(versions[1])
418	self.end_msg("%d.%d.%d" % (int(versions[1]) / 100000,
419							   int(versions[1]) / 100 % 1000,
420							   int(versions[1]) % 100))
421	if Logs.verbose:
422		Logs.pprint('CYAN', '	path : %s' % self.env['INCLUDES_%s' % var])
423
424	if not params['lib'] and not params['stlib']:
425		return
426	if 'static' in kw or 'static' in params:
427		Logs.warn('boost: static parameter is deprecated, use stlib instead.')
428	self.start_msg('Checking boost libs')
429	path, libs, stlibs = self.boost_get_libs(**params)
430	self.env['LIBPATH_%s' % var] = [path]
431	self.env['STLIBPATH_%s' % var] = [path]
432	self.env['LIB_%s' % var] = libs
433	self.env['STLIB_%s' % var] = stlibs
434	self.end_msg('ok')
435	if Logs.verbose:
436		Logs.pprint('CYAN', '	path : %s' % path)
437		Logs.pprint('CYAN', '	shared libs : %s' % libs)
438		Logs.pprint('CYAN', '	static libs : %s' % stlibs)
439
440	def has_shlib(lib):
441		return params['lib'] and lib in params['lib']
442	def has_stlib(lib):
443		return params['stlib'] and lib in params['stlib']
444	def has_lib(lib):
445		return has_shlib(lib) or has_stlib(lib)
446	if has_lib('thread'):
447		# not inside try_link to make check visible in the output
448		self._check_pthread_flag(k, kw)
449
450	def try_link():
451		if has_lib('system'):
452			self.check_cxx(fragment=BOOST_ERROR_CODE, use=var, execute=False)
453		if has_lib('thread'):
454			self.check_cxx(fragment=BOOST_THREAD_CODE, use=var, execute=False)
455		if has_lib('log'):
456			if not has_lib('thread'):
457				self.env['DEFINES_%s' % var] += ['BOOST_LOG_NO_THREADS']
458			if has_shlib('log'):
459				self.env['DEFINES_%s' % var] += ['BOOST_LOG_DYN_LINK']
460			self.check_cxx(fragment=BOOST_LOG_CODE, use=var, execute=False)
461
462	if params.get('linkage_autodetect', False):
463		self.start_msg("Attempting to detect boost linkage flags")
464		toolset = self.boost_get_toolset(kw.get('toolset', ''))
465		if toolset in ('vc',):
466			# disable auto-linking feature, causing error LNK1181
467			# because the code wants to be linked against
468			self.env['DEFINES_%s' % var] += ['BOOST_ALL_NO_LIB']
469
470			# if no dlls are present, we guess the .lib files are not stubs
471			has_dlls = False
472			for x in Utils.listdir(path):
473				if x.endswith(self.env.cxxshlib_PATTERN % ''):
474					has_dlls = True
475					break
476			if not has_dlls:
477				self.env['STLIBPATH_%s' % var] = [path]
478				self.env['STLIB_%s' % var] = libs
479				del self.env['LIB_%s' % var]
480				del self.env['LIBPATH_%s' % var]
481
482			# we attempt to play with some known-to-work CXXFLAGS combinations
483			for cxxflags in (['/MD', '/EHsc'], []):
484				self.env.stash()
485				self.env["CXXFLAGS_%s" % var] += cxxflags
486				try:
487					try_link()
488				except Errors.ConfigurationError as e:
489					self.env.revert()
490					exc = e
491				else:
492					self.end_msg("ok: winning cxxflags combination: %s" % (self.env["CXXFLAGS_%s" % var]))
493					exc = None
494					self.env.commit()
495					break
496
497			if exc is not None:
498				self.end_msg("Could not auto-detect boost linking flags combination, you may report it to boost.py author", ex=exc)
499				self.fatal('The configuration failed')
500		else:
501			self.end_msg("Boost linkage flags auto-detection not implemented (needed ?) for this toolchain")
502			self.fatal('The configuration failed')
503	else:
504		self.start_msg('Checking for boost linkage')
505		try:
506			try_link()
507		except Errors.ConfigurationError as e:
508			self.end_msg("Could not link against boost libraries using supplied options")
509			self.fatal('The configuration failed')
510		self.end_msg('ok')
511
512
513@feature('cxx')
514@after_method('apply_link')
515def install_boost(self):
516	if install_boost.done or not Utils.is_win32 or not self.bld.cmd.startswith('install'):
517		return
518	install_boost.done = True
519	inst_to = getattr(self, 'install_path', '${BINDIR}')
520	for lib in self.env.LIB_BOOST:
521		try:
522			file = self.bld.find_file(self.env.cxxshlib_PATTERN % lib, self.env.LIBPATH_BOOST)
523			self.add_install_files(install_to=inst_to, install_from=self.bld.root.find_node(file))
524		except:
525			continue
526install_boost.done = False
527