More SCons/Configure cleanup
[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     ret = context.TryRun("#include <boost/version.hpp>\n"
146                          "#include <iostream>\n"
147                          "int main(int, char **) { std::cout << BOOST_LIB_VERSION << std::endl; }",
148                          ".cc")[-1].strip()
149
150     if not ret:
151         msg = "no boost includes found"
152         context.env.Replace( BOOST_VERSION = None )
153     else:
154         context.env.Replace( BOOST_VERSION = ret )
155         msg = ret
156         if min or max:
157             try: version = map(int,ret.split('_'))
158             except ValueError:
159                 msg = "[%s] invalid version syntax" % ret
160                 ret = None
161             else:
162                 if min : ret = ret and (version>=map(int,min.split('_')))
163                 if max : ret = ret and (version<=map(int,max.split('_')))
164                 msg = '[%s] %s' % (msg, ret and "yes" or "no")
165     context.Result(msg)
166     if fail and not ret : context.env.Fail('No valid boost includes found')
167     return ret
168
169 @ConfTest
170 def CheckBoostVariants(context, *variants):
171     if not variants : variants = ('','mt')
172     useVariant = None
173     if context.env['BOOST_VARIANT'] is not None:
174         useVariant = context.env['BOOST_VARIANT']
175     try:
176         _env = context.env.Clone()
177         for variant in variants:
178             if variant : variantStr = "'%s'" % variant
179             else       : variantStr = "default"
180             context.Message( "Checking boost %s variant... " % variantStr )
181             context.env.Replace( BOOST_VARIANT=variant )
182             context.env.Append( _LIBFLAGS = ' -Wl,-Bstatic -l$BOOSTTESTLIB  -Wl,-Bdynamic' )
183             ret = context.TryLink("#define BOOST_AUTO_TEST_MAIN\n"
184                                   "#include <boost/test/auto_unit_test.hpp>\n"
185                                   "#include <boost/test/test_tools.hpp>\n"
186                                   "BOOST_AUTO_TEST_CASE(test) { BOOST_CHECK(true); }\n",
187                                   ".cc")
188             context.Result( ret )
189             if ret and useVariant is None:
190                 useVariant = variant
191     finally:
192         context.env.Replace(**_env.Dictionary())
193     if useVariant is not None and not context.env.GetOption('no_progress'):
194         print  "Using %s boost variant." % (
195             useVariant and "'%s'" % useVariant or "default")
196     context.env.Replace( BOOST_VARIANT = useVariant )
197     return useVariant
198
199 def generate(env):
200     env.SetDefault(
201         BOOST_VARIANT     = None,
202         _BOOST_VARIANT    = '${BOOST_VARIANT and "-" or None}$BOOST_VARIANT',
203
204         BOOSTTESTLIB      = 'boost_unit_test_framework$_BOOST_VARIANT',
205         BOOSTREGEXLIB     = 'boost_regex$_BOOST_VARIANT',
206         BOOSTFSLIB        = 'boost_filesystem$_BOOST_VARIANT',
207         BOOSTIOSTREAMSLIB = 'boost_iostreams$_BOOST_VARIANT',
208         BOOSTSIGNALSLIB   = 'boost_signals$_BOOST_VARIANT',
209
210         BOOSTTESTARGS     = [ '--build_info=yes', '--log_level=test_suite' ],
211         )
212     env.Append(
213         CUSTOM_TESTS      = ConfTest.tests,
214         )
215
216     env['BUILDERS']['BoostUnitTest'] = BoostUnitTest
217     env['BUILDERS']['FindAllBoostUnitTests'] = FindAllBoostUnitTests
218     env['BUILDERS']['CompileCheck'] = env.Builder(
219         action = CompileCheck,
220         suffix = '.checked',
221         src_suffix = '.cc',
222         source_scanner = SCons.Scanner.C.CScanner(),
223         single_source=1
224         )
225     env['BUILDERS']['NopAction'] = NopAction
226
227 def exists(env):
228     return True