boost.py 18 KB

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