blob: 305945abb7731b4dc37f83335cb3b33a0feafd9b [file] [log] [blame]
taylorchuc27dd482014-05-17 20:06:49 -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-static and --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
55
56BOOST_LIBS = ['/usr/lib', '/usr/local/lib', '/opt/local/lib', '/sw/lib', '/lib', '/usr/lib/x86_64-linux-gnu', '/usr/lib/i386-linux-gnu', '/usr/local/ndn/lib']
57BOOST_INCLUDES = ['/usr/include', '/usr/local/include', '/opt/local/include', '/sw/include', '/usr/local/ndn/include']
58BOOST_VERSION_FILE = 'boost/version.hpp'
59BOOST_VERSION_CODE = '''
60#include <iostream>
61#include <boost/version.hpp>
62int main() { std::cout << BOOST_LIB_VERSION << ":" << BOOST_VERSION << std::endl; }
63'''
64BOOST_SYSTEM_CODE = '''
65#include <boost/system/error_code.hpp>
66int main() { boost::system::error_code c; }
67'''
68BOOST_THREAD_CODE = '''
69#include <boost/thread.hpp>
70int main() { boost::thread t; }
71'''
72
73# toolsets from {boost_dir}/tools/build/v2/tools/common.jam
74PLATFORM = Utils.unversioned_sys_platform()
75detect_intel = lambda env: (PLATFORM == 'win32') and 'iw' or 'il'
76detect_clang = lambda env: (PLATFORM == 'darwin') and 'clang-darwin' or 'clang'
77detect_mingw = lambda env: (re.search('MinGW', env.CXX[0])) and 'mgw' or 'gcc'
78BOOST_TOOLSETS = {
79 'borland': 'bcb',
80 'clang': detect_clang,
81 'como': 'como',
82 'cw': 'cw',
83 'darwin': 'xgcc',
84 'edg': 'edg',
85 'g++': detect_mingw,
86 'gcc': detect_mingw,
87 'icpc': detect_intel,
88 'intel': detect_intel,
89 'kcc': 'kcc',
90 'kylix': 'bck',
91 'mipspro': 'mp',
92 'mingw': 'mgw',
93 'msvc': 'vc',
94 'qcc': 'qcc',
95 'sun': 'sw',
96 'sunc++': 'sw',
97 'tru64cxx': 'tru',
98 'vacpp': 'xlc'
99}
100
101
102def options(opt):
103 opt = opt.add_option_group('Boost Options')
104
105 opt.add_option('--boost-includes', type='string',
106 default='', dest='boost_includes',
107 help='''path to the directory where the boost includes are, e.g., /path/to/boost_1_55_0/stage/include''')
108 opt.add_option('--boost-libs', type='string',
109 default='', dest='boost_libs',
110 help='''path to the directory where the boost libs are, e.g., /path/to/boost_1_55_0/stage/lib''')
111 opt.add_option('--boost-static', action='store_true',
112 default=False, dest='boost_static',
113 help='link with static boost libraries (.lib/.a)')
114 opt.add_option('--boost-mt', action='store_true',
115 default=False, dest='boost_mt',
116 help='select multi-threaded libraries')
117 opt.add_option('--boost-abi', type='string', default='', dest='boost_abi',
118 help='''select libraries with tags (dgsyp, d for debug), see doc Boost, Getting Started, chapter 6.1''')
119 opt.add_option('--boost-linkage_autodetect', action="store_true", dest='boost_linkage_autodetect',
120 help="auto-detect boost linkage options (don't get used to it / might break other stuff)")
121 opt.add_option('--boost-toolset', type='string',
122 default='', dest='boost_toolset',
123 help='force a toolset e.g. msvc, vc90, gcc, mingw, mgw45 (default: auto)')
124 py_version = '%d%d' % (sys.version_info[0], sys.version_info[1])
125 opt.add_option('--boost-python', type='string',
126 default=py_version, dest='boost_python',
127 help='select the lib python with this version (default: %s)' % py_version)
128
129
130@conf
131def __boost_get_version_file(self, d):
132 dnode = self.root.find_dir(d)
133 if dnode:
134 return dnode.find_node(BOOST_VERSION_FILE)
135 return None
136
137@conf
138def boost_get_version(self, d):
139 """silently retrieve the boost version number"""
140 node = self.__boost_get_version_file(d)
141 if node:
142 try:
143 txt = node.read()
144 except (OSError, IOError):
145 Logs.error("Could not read the file %r" % node.abspath())
146 else:
147 re_but1 = re.compile('^#define\\s+BOOST_LIB_VERSION\\s+"(.+)"', re.M)
148 m1 = re_but1.search(txt)
149
150 re_but2 = re.compile('^#define\\s+BOOST_VERSION\\s+(\\d+)', re.M)
151 m2 = re_but2.search(txt)
152
153 if m1 and m2:
154 return (m1.group(1), m2.group(1))
155
156 return self.check_cxx(fragment=BOOST_VERSION_CODE, includes=[d], execute=True, define_ret=True).split(":")
157
158@conf
159def boost_get_includes(self, *k, **kw):
160 includes = k and k[0] or kw.get('includes', None)
161 if includes and self.__boost_get_version_file(includes):
162 return includes
163 for d in Utils.to_list(self.environ.get('INCLUDE', '')) + BOOST_INCLUDES:
164 if self.__boost_get_version_file(d):
165 return d
166 if includes:
167 self.end_msg('headers not found in %s' % includes)
168 self.fatal('The configuration failed')
169 else:
170 self.end_msg('headers not found, please provide a --boost-includes argument (see help)')
171 self.fatal('The configuration failed')
172
173
174@conf
175def boost_get_toolset(self, cc):
176 toolset = cc
177 if not cc:
178 build_platform = Utils.unversioned_sys_platform()
179 if build_platform in BOOST_TOOLSETS:
180 cc = build_platform
181 else:
182 cc = self.env.CXX_NAME
183 if cc in BOOST_TOOLSETS:
184 toolset = BOOST_TOOLSETS[cc]
185 return isinstance(toolset, str) and toolset or toolset(self.env)
186
187
188@conf
189def __boost_get_libs_path(self, *k, **kw):
190 ''' return the lib path and all the files in it '''
191 if 'files' in kw:
192 return self.root.find_dir('.'), Utils.to_list(kw['files'])
193 libs = k and k[0] or kw.get('libs', None)
194 if libs:
195 path = self.root.find_dir(libs)
196 files = path.ant_glob('*boost_*')
197 if not libs or not files:
198 for d in Utils.to_list(self.environ.get('LIB', [])) + BOOST_LIBS:
199 path = self.root.find_dir(d)
200 if path:
201 files = path.ant_glob('*boost_*')
202 if files:
203 break
204 path = self.root.find_dir(d + '64')
205 if path:
206 files = path.ant_glob('*boost_*')
207 if files:
208 break
209 if not path:
210 if libs:
211 self.end_msg('libs not found in %s' % libs)
212 self.fatal('The configuration failed')
213 else:
214 self.end_msg('libs not found, please provide a --boost-libs argument (see help)')
215 self.fatal('The configuration failed')
216
217 self.to_log('Found the boost path in %r with the libraries:' % path)
218 for x in files:
219 self.to_log(' %r' % x)
220 return path, files
221
222@conf
223def boost_get_libs(self, *k, **kw):
224 '''
225 return the lib path and the required libs
226 according to the parameters
227 '''
228 path, files = self.__boost_get_libs_path(**kw)
229 t = []
230 if kw.get('mt', False):
231 t.append('mt')
232 if kw.get('abi', None):
233 t.append(kw['abi'])
234 tags = t and '(-%s)+' % '-'.join(t) or ''
235 toolset = self.boost_get_toolset(kw.get('toolset', ''))
236 toolset_pat = '(-%s[0-9]{0,3})+' % toolset
237 version = '(-%s)+' % self.env.BOOST_VERSION
238
239 def find_lib(re_lib, files):
240 for file in files:
241 if re_lib.search(file.name):
242 self.to_log('Found boost lib %s' % file)
243 return file
244 return None
245
246 def format_lib_name(name):
247 if name.startswith('lib') and self.env.CC_NAME != 'msvc':
248 name = name[3:]
249 return name[:name.rfind('.')]
250
251 libs = []
252 for lib in Utils.to_list(k and k[0] or kw.get('lib', None)):
253 py = (lib == 'python') and '(-py%s)+' % kw['python'] or ''
254 # Trying libraries, from most strict match to least one
255 for pattern in ['boost_%s%s%s%s%s' % (lib, toolset_pat, tags, py, version),
256 'boost_%s%s%s%s' % (lib, tags, py, version),
257 'boost_%s%s%s' % (lib, tags, version),
258 # Give up trying to find the right version
259 'boost_%s%s%s%s' % (lib, toolset_pat, tags, py),
260 'boost_%s%s%s' % (lib, tags, py),
261 'boost_%s%s' % (lib, tags)]:
262 self.to_log('Trying pattern %s' % pattern)
263 file = find_lib(re.compile(pattern), files)
264 if file:
265 libs.append(format_lib_name(file.name))
266 break
267 else:
268 self.end_msg('lib %s not found in %s' % (lib, path.abspath()))
269 self.fatal('The configuration failed')
270
271 return path.abspath(), libs
272
273
274@conf
275def check_boost(self, *k, **kw):
276 """
277 Initialize boost libraries to be used.
278
279 Keywords: you can pass the same parameters as with the command line (without "--boost-").
280 Note that the command line has the priority, and should preferably be used.
281 """
282 if not self.env['CXX']:
283 self.fatal('load a c++ compiler first, conf.load("compiler_cxx")')
284
285 params = {'lib': k and k[0] or kw.get('lib', None)}
286 for key, value in self.options.__dict__.items():
287 if not key.startswith('boost_'):
288 continue
289 key = key[len('boost_'):]
290 params[key] = value and value or kw.get(key, '')
291
292 var = kw.get('uselib_store', 'BOOST')
293
294 self.start_msg('Checking boost includes')
295 self.env['INCLUDES_%s' % var] = inc = self.boost_get_includes(**params)
296 versions = self.boost_get_version(inc)
297 self.env.BOOST_VERSION = versions[0]
298 self.env.BOOST_VERSION_NUMBER = int(versions[1])
299 self.end_msg("%d.%d.%d" % (int(versions[1]) / 100000,
300 int(versions[1]) / 100 % 1000,
301 int(versions[1]) % 100))
302 if Logs.verbose:
303 Logs.pprint('CYAN', ' path : %s' % self.env['INCLUDES_%s' % var])
304
305 if not params['lib']:
306 return
307 self.start_msg('Checking boost libs')
308 suffix = params.get('static', None) and 'ST' or ''
309 path, libs = self.boost_get_libs(**params)
310 self.env['%sLIBPATH_%s' % (suffix, var)] = [path]
311 self.env['%sLIB_%s' % (suffix, var)] = libs
312 self.end_msg('ok')
313 if Logs.verbose:
314 Logs.pprint('CYAN', ' path : %s' % path)
315 Logs.pprint('CYAN', ' libs : %s' % libs)
316
317
318 def try_link():
319 if 'system' in params['lib']:
320 self.check_cxx(
321 fragment=BOOST_SYSTEM_CODE,
322 use=var,
323 execute=False,
324 )
325 if 'thread' in params['lib']:
326 self.check_cxx(
327 fragment=BOOST_THREAD_CODE,
328 use=var,
329 execute=False,
330 )
331
332 if params.get('linkage_autodetect', False):
333 self.start_msg("Attempting to detect boost linkage flags")
334 toolset = self.boost_get_toolset(kw.get('toolset', ''))
335 if toolset in ['vc']:
336 # disable auto-linking feature, causing error LNK1181
337 # because the code wants to be linked against
338 self.env['DEFINES_%s' % var] += ['BOOST_ALL_NO_LIB']
339
340 # if no dlls are present, we guess the .lib files are not stubs
341 has_dlls = False
342 for x in Utils.listdir(path):
343 if x.endswith(self.env.cxxshlib_PATTERN % ''):
344 has_dlls = True
345 break
346 if not has_dlls:
347 self.env['STLIBPATH_%s' % var] = [path]
348 self.env['STLIB_%s' % var] = libs
349 del self.env['LIB_%s' % var]
350 del self.env['LIBPATH_%s' % var]
351
352 # we attempt to play with some known-to-work CXXFLAGS combinations
353 for cxxflags in (['/MD', '/EHsc'], []):
354 self.env.stash()
355 self.env["CXXFLAGS_%s" % var] += cxxflags
356 try:
357 try_link()
358 self.end_msg("ok: winning cxxflags combination: %s" % (self.env["CXXFLAGS_%s" % var]))
359 e = None
360 break
361 except Errors.ConfigurationError as exc:
362 self.env.revert()
363 e = exc
364
365 if e is not None:
366 self.end_msg("Could not auto-detect boost linking flags combination, you may report it to boost.py author", ex=e)
367 self.fatal('The configuration failed')
368 else:
369 self.end_msg("Boost linkage flags auto-detection not implemented (needed ?) for this toolchain")
370 self.fatal('The configuration failed')
371 else:
372 self.start_msg('Checking for boost linkage')
373 try:
374 try_link()
375 except Errors.ConfigurationError as e:
376 self.end_msg("Could not link against boost libraries using supplied options")
377 self.fatal('The configuration failed')
378 self.end_msg('ok')