Reorganize SCons build system
[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):
125     context.Message( "Checking boost version... " )
126     ret = context.TryRun("#include <boost/version.hpp>\n"
127                          "#include <iostream>\n"
128                          "int main(int, char **) { std::cout << BOOST_LIB_VERSION << std::endl; }",
129                          ".cc")[-1].strip()
130     if not ret:
131         context.Result("no boost includes found")
132         context.env.Replace( BOOST_VERSION = '' )
133         return None
134     else:
135         context.Result(ret)
136         context.env.Replace( BOOST_VERSION = ret )
137         return ret
138
139 @ConfTest
140 def CheckBoostVariants(context, *variants):
141     if not variants : variants = ('','mt')
142     useVariant = None
143     if context.env['BOOST_VARIANT'] is not None:
144         useVariant = context.env['BOOST_VARIANT']
145     try:
146         _env = context.env.Clone()
147         for variant in variants:
148             if variant : variantStr = "'%s'" % variant
149             else       : variantStr = "default"
150             context.Message( "Checking boost %s variant... " % variantStr )
151             context.env.Replace( BOOST_VARIANT=variant )
152             context.env.Append( _LIBFLAGS = ' -Wl,-Bstatic -l$BOOSTTESTLIB  -Wl,-Bdynamic' )
153             ret = context.TryLink("#define BOOST_AUTO_TEST_MAIN\n"
154                                   "#include <boost/test/auto_unit_test.hpp>\n"
155                                   "#include <boost/test/test_tools.hpp>\n"
156                                   "BOOST_AUTO_TEST_CASE(test) { BOOST_CHECK(true); }\n",
157                                   ".cc")
158             context.Result( ret )
159             if ret and useVariant is None:
160                 useVariant = variant
161     finally:
162         context.env.Replace(**_env.Dictionary())
163     if useVariant is not None and not context.env.GetOption('no_progress'):
164         print  "Using %s boost variant." % (
165             useVariant and "'%s'" % useVariant or "default")
166     context.env.Replace( BOOST_VARIANT = useVariant )
167     return useVariant
168
169 def generate(env):
170     env.SetDefault(
171         BOOST_VARIANT     = None,
172         _BOOST_VARIANT    = '${BOOST_VARIANT and "-" or None}$BOOST_VARIANT',
173
174         BOOSTTESTLIB      = 'boost_unit_test_framework$_BOOST_VARIANT',
175         BOOSTREGEXLIB     = 'boost_regex$_BOOST_VARIANT',
176         BOOSTFSLIB        = 'boost_filesystem$_BOOST_VARIANT',
177         BOOSTIOSTREAMSLIB = 'boost_iostreams$_BOOST_VARIANT',
178         BOOSTSIGNALSLIB   = 'boost_signals$_BOOST_VARIANT',
179
180         BOOSTTESTARGS     = [ '--build_info=yes', '--log_level=test_suite' ],
181         )
182     env.Append(
183         CUSTOM_TESTS      = ConfTest.tests,
184         )
185
186     env['BUILDERS']['BoostUnitTest'] = BoostUnitTest
187     env['BUILDERS']['FindAllBoostUnitTests'] = FindAllBoostUnitTests
188     env['BUILDERS']['CompileCheck'] = env.Builder(
189         action = CompileCheck,
190         suffix = '.checked',
191         src_suffix = '.cc',
192         source_scanner = SCons.Scanner.C.CScanner(),
193         single_source=1
194         )
195     env['BUILDERS']['NopAction'] = NopAction
196
197 def exists(env):
198     return True