Include waf as an extracted source directory, rather than as a one-in-a-file script.
[samba.git] / buildtools / wafadmin / 3rdparty / boost.py
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 #
10 #def set_options(opt):
11 #       opt.tool_options('boost')
12 #       # ...
13 #
14 #def configure(conf):
15 #       # ... (e.g. conf.check_tool('g++'))
16 #       conf.check_tool('boost')
17 #   conf.check_boost(lib='signals filesystem', static='onlystatic', score_version=(-1000, 1000), tag_minscore=1000)
18 #
19 #def build(bld):
20 #   bld(source='main.c', target='bar', uselib="BOOST BOOST_SYSTEM")
21 #
22 #ISSUES:
23 # * find_includes should be called only once!
24 # * support mandatory
25
26 ######## boost update ###########
27 ## ITA: * the method get_boost_version_number does work
28 ##      * the rest of the code has not really been tried
29 #       * make certain a demo is provided (in demos/adv for example)
30
31 # TODO: bad and underdocumented code -> boost.py will be removed in waf 1.6 to be rewritten later
32
33 import os.path, glob, types, re, sys
34 import Configure, config_c, Options, Utils, Logs
35 from Logs import warn, debug
36 from Configure import conf
37
38 boost_code = '''
39 #include <iostream>
40 #include <boost/version.hpp>
41 int main() { std::cout << BOOST_VERSION << std::endl; }
42 '''
43
44 boost_libpath = ['/usr/lib', '/usr/local/lib', '/opt/local/lib', '/sw/lib', '/lib']
45 boost_cpppath = ['/usr/include', '/usr/local/include', '/opt/local/include', '/sw/include']
46
47 STATIC_NOSTATIC = 'nostatic'
48 STATIC_BOTH = 'both'
49 STATIC_ONLYSTATIC = 'onlystatic'
50
51 is_versiontag = re.compile('^\d+_\d+_?\d*$')
52 is_threadingtag = re.compile('^mt$')
53 is_abitag = re.compile('^[sgydpn]+$')
54 is_toolsettag = re.compile('^(acc|borland|como|cw|dmc|darwin|gcc|hp_cxx|intel|kylix|vc|mgw|qcc|sun|vacpp)\d*$')
55 is_pythontag=re.compile('^py[0-9]{2}$')
56
57 def set_options(opt):
58         opt.add_option('--boost-includes', type='string', default='', dest='boostincludes', help='path to the boost directory where the includes are e.g. /usr/local/include/boost-1_35')
59         opt.add_option('--boost-libs', type='string', default='', dest='boostlibs', help='path to the directory where the boost libs are e.g. /usr/local/lib')
60
61 def string_to_version(s):
62         version = s.split('.')
63         if len(version) < 3: return 0
64         return int(version[0])*100000 + int(version[1])*100 + int(version[2])
65
66 def version_string(version):
67         major = version / 100000
68         minor = version / 100 % 1000
69         minor_minor = version % 100
70         if minor_minor == 0:
71                 return "%d_%d" % (major, minor)
72         else:
73                 return "%d_%d_%d" % (major, minor, minor_minor)
74
75 def libfiles(lib, pattern, lib_paths):
76         result = []
77         for lib_path in lib_paths:
78                 libname = pattern % ('boost_%s[!_]*' % lib)
79                 result += glob.glob(os.path.join(lib_path, libname))
80         return result
81
82 @conf
83 def get_boost_version_number(self, dir):
84         """silently retrieve the boost version number"""
85         try:
86                 return self.run_c_code(compiler='cxx', code=boost_code, includes=dir, execute=1, env=self.env.copy(), type='cprogram', compile_mode='cxx', compile_filename='test.cpp')
87         except Configure.ConfigurationError, e:
88                 return -1
89
90 def set_default(kw, var, val):
91         if not var in kw:
92                 kw[var] = val
93
94 def tags_score(tags, kw):
95         """
96         checks library tags
97
98         see http://www.boost.org/doc/libs/1_35_0/more/getting_started/unix-variants.html 6.1
99         """
100         score = 0
101         needed_tags = {
102                 'threading': kw['tag_threading'],
103                 'abi':       kw['tag_abi'],
104                 'toolset':   kw['tag_toolset'],
105                 'version':   kw['tag_version'],
106                 'python':    kw['tag_python']
107         }
108
109         if kw['tag_toolset'] is None:
110                 v = kw['env']
111                 toolset = v['CXX_NAME']
112                 if v['CXX_VERSION']:
113                         version_no = v['CXX_VERSION'].split('.')
114                         toolset += version_no[0]
115                         if len(version_no) > 1:
116                                 toolset += version_no[1]
117                 needed_tags['toolset'] = toolset
118
119         found_tags = {}
120         for tag in tags:
121                 if is_versiontag.match(tag): found_tags['version'] = tag
122                 if is_threadingtag.match(tag): found_tags['threading'] = tag
123                 if is_abitag.match(tag): found_tags['abi'] = tag
124                 if is_toolsettag.match(tag): found_tags['toolset'] = tag
125                 if is_pythontag.match(tag): found_tags['python'] = tag
126
127         for tagname in needed_tags.iterkeys():
128                 if needed_tags[tagname] is not None and tagname in found_tags:
129                         if re.compile(needed_tags[tagname]).match(found_tags[tagname]):
130                                 score += kw['score_' + tagname][0]
131                         else:
132                                 score += kw['score_' + tagname][1]
133         return score
134
135 @conf
136 def validate_boost(self, kw):
137         ver = kw.get('version', '')
138
139         for x in 'min_version max_version version'.split():
140                 set_default(kw, x, ver)
141
142         set_default(kw, 'lib', '')
143         kw['lib'] = Utils.to_list(kw['lib'])
144
145         set_default(kw, 'env', self.env)
146
147         set_default(kw, 'libpath', boost_libpath)
148         set_default(kw, 'cpppath', boost_cpppath)
149
150         for x in 'tag_threading tag_version tag_toolset'.split():
151                 set_default(kw, x, None)
152         set_default(kw, 'tag_abi', '^[^d]*$')
153
154         set_default(kw, 'python', str(sys.version_info[0]) + str(sys.version_info[1]) )
155         set_default(kw, 'tag_python', '^py' + kw['python'] + '$')
156
157         set_default(kw, 'score_threading', (10, -10))
158         set_default(kw, 'score_abi', (10, -10))
159         set_default(kw, 'score_python', (10,-10))
160         set_default(kw, 'score_toolset', (1, -1))
161         set_default(kw, 'score_version', (100, -100))
162
163         set_default(kw, 'score_min', 0)
164         set_default(kw, 'static', STATIC_NOSTATIC)
165         set_default(kw, 'found_includes', False)
166         set_default(kw, 'min_score', 0)
167
168         set_default(kw, 'errmsg', 'not found')
169         set_default(kw, 'okmsg', 'ok')
170
171 @conf
172 def find_boost_includes(self, kw):
173         """
174         check every path in kw['cpppath'] for subdir
175         that either starts with boost- or is named boost.
176
177         Then the version is checked and selected accordingly to
178         min_version/max_version. The highest possible version number is
179         selected!
180
181         If no versiontag is set the versiontag is set accordingly to the
182         selected library and CPPPATH_BOOST is set.
183         """
184         boostPath = getattr(Options.options, 'boostincludes', '')
185         if boostPath:
186                 boostPath = [os.path.normpath(os.path.expandvars(os.path.expanduser(boostPath)))]
187         else:
188                 boostPath = Utils.to_list(kw['cpppath'])
189
190         min_version = string_to_version(kw.get('min_version', ''))
191         max_version = string_to_version(kw.get('max_version', '')) or (sys.maxint - 1)
192
193         version = 0
194         for include_path in boostPath:
195                 boost_paths = [p for p in glob.glob(os.path.join(include_path, 'boost*')) if os.path.isdir(p)]
196                 debug('BOOST Paths: %r' % boost_paths)
197                 for path in boost_paths:
198                         pathname = os.path.split(path)[-1]
199                         ret = -1
200                         if pathname == 'boost':
201                                 path = include_path
202                                 ret = self.get_boost_version_number(path)
203                         elif pathname.startswith('boost-'):
204                                 ret = self.get_boost_version_number(path)
205                         ret = int(ret)
206
207                         if ret != -1 and ret >= min_version and ret <= max_version and ret > version:
208                                 boost_path = path
209                                 version = ret
210         if not version:
211                 self.fatal('boost headers not found! (required version min: %s max: %s)'
212                           % (kw['min_version'], kw['max_version']))
213                 return False
214
215         found_version = version_string(version)
216         versiontag = '^' + found_version + '$'
217         if kw['tag_version'] is None:
218                 kw['tag_version'] = versiontag
219         elif kw['tag_version'] != versiontag:
220                 warn('boost header version %r and tag_version %r do not match!' % (versiontag, kw['tag_version']))
221         env = self.env
222         env['CPPPATH_BOOST'] = boost_path
223         env['BOOST_VERSION'] = found_version
224         self.found_includes = 1
225         ret = 'Version %s (%s)' % (found_version, boost_path)
226         return ret
227
228 @conf
229 def find_boost_library(self, lib, kw):
230
231         def find_library_from_list(lib, files):
232                 lib_pattern = re.compile('.*boost_(.*?)\..*')
233                 result = (None, None)
234                 resultscore = kw['min_score'] - 1
235                 for file in files:
236                         m = lib_pattern.search(file, 1)
237                         if m:
238                                 libname = m.group(1)
239                                 libtags = libname.split('-')[1:]
240                                 currentscore = tags_score(libtags, kw)
241                                 if currentscore > resultscore:
242                                         result = (libname, file)
243                                         resultscore = currentscore
244                 return result
245
246         lib_paths = getattr(Options.options, 'boostlibs', '')
247         if lib_paths:
248                 lib_paths = [os.path.normpath(os.path.expandvars(os.path.expanduser(lib_paths)))]
249         else:
250                 lib_paths = Utils.to_list(kw['libpath'])
251
252         v = kw.get('env', self.env)
253
254         (libname, file) = (None, None)
255         if kw['static'] in [STATIC_NOSTATIC, STATIC_BOTH]:
256                 st_env_prefix = 'LIB'
257                 files = libfiles(lib, v['shlib_PATTERN'], lib_paths)
258                 (libname, file) = find_library_from_list(lib, files)
259         if libname is None and kw['static'] in [STATIC_ONLYSTATIC, STATIC_BOTH]:
260                 st_env_prefix = 'STATICLIB'
261                 staticLibPattern = v['staticlib_PATTERN']
262                 if self.env['CC_NAME'] == 'msvc':
263                         staticLibPattern = 'lib' + staticLibPattern
264                 files = libfiles(lib, staticLibPattern, lib_paths)
265                 (libname, file) = find_library_from_list(lib, files)
266         if libname is not None:
267                 v['LIBPATH_BOOST_' + lib.upper()] = [os.path.split(file)[0]]
268                 if self.env['CC_NAME'] == 'msvc' and os.path.splitext(file)[1] == '.lib':
269                         v[st_env_prefix + '_BOOST_' + lib.upper()] = ['libboost_'+libname]
270                 else:
271                         v[st_env_prefix + '_BOOST_' + lib.upper()] = ['boost_'+libname]
272                 return
273         self.fatal('lib boost_' + lib + ' not found!')
274
275 @conf
276 def check_boost(self, *k, **kw):
277         """
278         This should be the main entry point
279
280 - min_version
281 - max_version
282 - version
283 - include_path
284 - lib_path
285 - lib
286 - toolsettag   - None or a regexp
287 - threadingtag - None or a regexp
288 - abitag       - None or a regexp
289 - versiontag   - WARNING: you should rather use version or min_version/max_version
290 - static       - look for static libs (values:
291           'nostatic'   or STATIC_NOSTATIC   - ignore static libs (default)
292           'both'       or STATIC_BOTH       - find static libs, too
293           'onlystatic' or STATIC_ONLYSTATIC - find only static libs
294 - score_version
295 - score_abi
296 - scores_threading
297 - score_toolset
298  * the scores are tuples (match_score, nomatch_score)
299    match_score is the added to the score if the tag is matched
300    nomatch_score is added when a tag is found and does not match
301 - min_score
302         """
303
304         if not self.env['CXX']:
305                 self.fatal('load a c++ compiler tool first, for example conf.check_tool("g++")')
306         self.validate_boost(kw)
307         ret = None
308         try:
309                 if not kw.get('found_includes', None):
310                         self.check_message_1(kw.get('msg_includes', 'boost headers'))
311                         ret = self.find_boost_includes(kw)
312
313         except Configure.ConfigurationError, e:
314                 if 'errmsg' in kw:
315                         self.check_message_2(kw['errmsg'], 'YELLOW')
316                 if 'mandatory' in kw:
317                         if Logs.verbose > 1:
318                                 raise
319                         else:
320                                 self.fatal('the configuration failed (see %r)' % self.log.name)
321         else:
322                 if 'okmsg' in kw:
323                         self.check_message_2(kw.get('okmsg_includes', ret))
324
325         for lib in kw['lib']:
326                 self.check_message_1('library boost_'+lib)
327                 try:
328                         self.find_boost_library(lib, kw)
329                 except Configure.ConfigurationError, e:
330                         ret = False
331                         if 'errmsg' in kw:
332                                 self.check_message_2(kw['errmsg'], 'YELLOW')
333                         if 'mandatory' in kw:
334                                 if Logs.verbose > 1:
335                                         raise
336                                 else:
337                                         self.fatal('the configuration failed (see %r)' % self.log.name)
338                 else:
339                         if 'okmsg' in kw:
340                                 self.check_message_2(kw['okmsg'])
341
342         return ret
343