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