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