7e20e2bbc1ce7a31d1763af0848d3ccaee91ddc5
[senf.git] / site_scons / site_tools / Boost.py
1 import SCons.Script
2 import SCons.Script.SConscript
3 import SCons.Defaults
4 import os.path
5 import os
6 import sys
7 import tempfile
8 import SCons.Scanner.C
9 import CustomTests
10
11 # ARGH ... Why do they put a '+' in the module name ????????
12 SCons.Tool.cplusplus=getattr(__import__('SCons.Tool.c++', globals(), locals(), []).Tool, 'c++')
13
14 _ALL_TESTS = []
15
16 def scanTests(f):
17     tests = {}
18     name = start = None
19     linenr= 0
20     for line in f:
21         linenr += 1
22         if line.startswith('COMPILE_FAIL(') and ')' in line:
23             name = line.split('(',1)[-1].split(')',1)[0]
24             start = linenr
25         elif line.startswith('}') and name and start:
26             tests[name] = (start, linenr)
27     return tests
28
29 def CompileCheck(target, source, env):
30     tests = scanTests(file(source[0].abspath))
31     cenv = env.Clone()
32     cenv.Append( CPPDEFINES = [ 'COMPILE_CHECK' ] )
33     out = tempfile.TemporaryFile()
34     cenv['SPAWN'] = lambda sh, escape, cmd, args, env, pspawn=cenv['PSPAWN'], out=out: \
35                     pspawn(sh, escape, cmd, args, env, out, out)
36     SCons.Script.Action('$CXXCOM').execute(target, source, cenv)
37     passedTests = {}
38     delay_name = None
39     out.seek(0)
40     result = out.read();
41     for error in result.splitlines():
42         elts = error.split(':',2)
43         if len(elts) != 3 : continue
44         filename, line, message = elts
45         if not os.path.exists(filename) : continue
46         try: line = int(line)
47         except ValueError : continue
48         message = message.strip()
49
50         if delay_name and not message.startswith('instantiated from '):
51             print "Passed test '%s': %s" % (delay_name, message)
52             delay_name = None
53             continue
54
55         filename = os.path.abspath(filename)
56         if filename != source[0].abspath : continue
57
58         for name,lines in tests.iteritems():
59             if line >= lines[0] and line <= lines[1]:
60                 passedTests[name] = 1
61                 if message.startswith('instantiated from '):
62                     delay_name = name
63                 else:
64                     print "Passed test '%s': %s" % (name, message)
65     if delay_name:
66         print "Passed test '%s': <unknown message ??>" % delay_name
67     failedTests = set(tests.iterkeys()) - set(passedTests.iterkeys())
68     if failedTests:
69         for test in failedTests:
70             print "Test '%s' FAILED" % test
71         print
72         print "*** %d tests FAILED" % len(failedTests)
73         if os.path.exists(target[0].abspath):
74             os.unlink(target[0].abspath)
75         return 1
76     file(target[0].abspath,"w").write(result)
77     return 0
78
79 CompileCheck = SCons.Script.Action(CompileCheck)
80
81 def BoostUnitTest(env, target=None, source=None,  **kw):
82     global _ALL_TESTS
83
84     target = env.arg2nodes(target)[0]
85     source = env.arg2nodes(source)
86
87     binnode = target.dir.File('.' + target.name + '.bin')
88     stampnode = target.dir.File('.' + target.name + '.stamp')
89
90     bin = env.Program(binnode, source,
91                       LIBS = env['LIBS'] + [ '$TEST_EXTRA_LIBS' ],
92                       _LIBFLAGS = ' -Wl,-Bstatic -l$BOOSTTESTLIB -Wl,-Bdynamic ' + env['_LIBFLAGS'],
93                       **kw)
94
95     stamp = env.Command(stampnode, bin,
96                         [ './$SOURCE $BOOSTTESTARGS', SCons.Script.Touch('$TARGET')],
97                         **kw)
98
99     alias = env.Command(env.File(target), stamp, [ env.NopAction() ] )
100
101     compileTests = [ src for src in source
102                      if src.suffix in SCons.Tool.cplusplus.CXXSuffixes \
103                          and src.exists() \
104                          and 'COMPILE_CHECK' in file(str(src)).read() ]
105     if compileTests:
106         env.Depends(alias, env.CompileCheck(source = compileTests))
107
108     _ALL_TESTS.append(alias)
109
110     return alias
111
112 def FindAllBoostUnitTests(env, target, source):
113     global _ALL_TESTS
114     return _ALL_TESTS
115
116 def NopAction(env, target, source):
117     def nop(target, source, env) : return None
118     def nopstr(target, source, env) : return ''
119     return env.Action(nop, nopstr)
120
121 ConfTest = CustomTests.ConfTest()
122
123 @ConfTest
124 def CheckBoostVersion(context,fail=False,min=None,max=None):
125     """Check for boost includes.
126
127 Will place the boost version number (BOOST_LIB_VERSION) into the
128 BOOST_VERSION environment variable.
129
130 Options:
131
132     min/max   compare boost version against given range.
133
134     fail      if fail is set to True, the build will be terminated,
135               when no valid boost includes are found."""
136     if min and max:
137         msg = ' in range %s to %s' % (min,max)
138     elif min:
139         msg = ' at least %s' % min
140     elif max:
141         msg = ' at most %s' % max
142     else:
143         msg = ''
144     context.Message( "Checking boost version%s... " % msg )
145     if context.env.has_key('BOOST_VERSION'):
146         ret = context.env['BOOST_VERSION']
147     else:
148         ret = context.TryRun("#include <boost/version.hpp>\n"
149                              "#include <iostream>\n"
150                              "int main(int, char **)\n"
151                              "{ std::cout << BOOST_LIB_VERSION << std::endl; }",
152                              ".cc")[-1].strip()
153
154     if not ret:
155         msg = "no boost includes found"
156         context.env.Replace( BOOST_VERSION = None )
157     else:
158         context.env.Replace( BOOST_VERSION = ret )
159         msg = ret
160         if min or max:
161             try: version = map(int,ret.split('_'))
162             except ValueError:
163                 msg = "[%s] invalid version syntax" % ret
164                 ret = None
165             else:
166                 if min : ret = ret and (version>=map(int,min.split('_')))
167                 if max : ret = ret and (version<=map(int,max.split('_')))
168                 msg = '[%s] %s' % (msg, ret and "yes" or "no")
169     context.Result(msg)
170     if fail and not ret : context.env.Fail('No valid boost includes found')
171     return ret
172
173 @ConfTest
174 def CheckBoostVariants(context, *variants):
175     if not variants : variants = ('','mt')
176     useVariant = None
177     if context.env['BOOST_VARIANT'] is not None:
178         useVariant = context.env['BOOST_VARIANT']
179     try:
180         _env = context.env.Clone()
181         for variant in variants:
182             if variant : variantStr = "'%s'" % variant
183             else       : variantStr = "default"
184             context.Message( "Checking boost %s variant... " % variantStr )
185             context.env.Replace( BOOST_VARIANT=variant )
186             context.env.Append( _LIBFLAGS = ' -Wl,-Bstatic -l$BOOSTTESTLIB  -Wl,-Bdynamic' )
187             ret = context.TryLink("#define BOOST_AUTO_TEST_MAIN\n"
188                                   "#include <boost/test/auto_unit_test.hpp>\n"
189                                   "#include <boost/test/test_tools.hpp>\n"
190                                   "BOOST_AUTO_TEST_CASE(test) { BOOST_CHECK(true); }\n",
191                                   ".cc")
192             context.Result( ret )
193             if ret and useVariant is None:
194                 useVariant = variant
195     finally:
196         context.env.Replace(**_env.Dictionary())
197     if useVariant is not None and not context.env.GetOption('no_progress'):
198         print  "Using %s boost variant." % (
199             useVariant and "'%s'" % useVariant or "default")
200     context.env.Replace( BOOST_VARIANT = useVariant )
201     return useVariant
202
203 def generate(env):
204     env.SetDefault(
205         BOOST_VARIANT     = None,
206         _BOOST_VARIANT    = '${BOOST_VARIANT and "-" or None}$BOOST_VARIANT',
207
208         BOOSTTESTLIB      = 'boost_unit_test_framework$_BOOST_VARIANT',
209         BOOSTREGEXLIB     = 'boost_regex$_BOOST_VARIANT',
210         BOOSTFSLIB        = 'boost_filesystem$_BOOST_VARIANT',
211         BOOSTIOSTREAMSLIB = 'boost_iostreams$_BOOST_VARIANT',
212         BOOSTSIGNALSLIB   = 'boost_signals$_BOOST_VARIANT',
213
214         BOOSTTESTARGS     = [ '--build_info=yes', '--log_level=test_suite' ],
215         )
216     env.Append(
217         CUSTOM_TESTS      = ConfTest.tests,
218         )
219
220     env['BUILDERS']['BoostUnitTest'] = BoostUnitTest
221     env['BUILDERS']['FindAllBoostUnitTests'] = FindAllBoostUnitTests
222     env['BUILDERS']['CompileCheck'] = env.Builder(
223         action = CompileCheck,
224         suffix = '.checked',
225         src_suffix = '.cc',
226         source_scanner = SCons.Scanner.C.CScanner(),
227         single_source=1
228         )
229     env['BUILDERS']['NopAction'] = NopAction
230
231 def exists(env):
232     return True