minor fixes for clang++
[senf.git] / site_scons / SparseTestHack.py
1 import SCons.Node, SCons.Node.FS, SCons.Util, SCons.Errors, os
2
3 #####################################################################
4 # This IS a hack .. but a very useful one: The hack will remove the
5 # 'senf' library from the unit-tests and instead add all the object
6 # files needed explicitly.
7 #
8 # This works by building a list of all children (recursively) of the
9 # test sources. For each child we check, whether a file with the same
10 # name but an $OBJSUFFIX extension exists as a build target. In that
11 # case, we add it to the list of needed objects (AND recursively scan
12 # that objects children).
13 #
14 # Additionally, of the variable 'only_tests' is set, it contains a
15 # list of test source files to be built exclusively: All other test
16 # sources will NOT be compiled (and their children will therefore not
17 # be scanned).
18 #
19 # There is one restriction: CompileCheck tests are not run when this
20 # hack is enabled.
21 #####################################################################
22
23 # Build a list of all object file targets which are needed by 'nodes'
24 def neededObjects(env,nodes):
25     if not SCons.Util.is_List(nodes) : nodes = [nodes]
26
27     def walk(nodes):
28         for node in nodes:
29             walker = SCons.Node.Walker(node)
30             while True:
31                 n = walker.next()
32                 if n is None : break
33                 yield n
34
35     seen = {}
36     rv = {}
37
38     def check(nodes, env=env, seen=seen, rv=rv):
39         for c in walk(nodes):
40             if isinstance(c,SCons.Node.FS.File) and not seen.get(c):
41                 seen[c] = True
42                 ob = c.File(env.subst("${SOURCE.dir.abspath}/${OBJPREFIX}${SOURCE.filebase}${OBJSUFFIX}", source=c))
43                 if ob.is_derived():
44                     rv[ob] = True
45                     if not seen.get(ob):
46                         seen[ob] = True
47                         check([ob])
48
49     check(nodes)
50
51     for node in nodes:
52         rv[node] = True
53     rv = [ (str(x),x) for x in rv.iterkeys() ]
54     rv.sort()
55     return (x[1] for x in rv)
56
57 # Return a map witch has all sources (recursively) of 'nodes' as keys
58 def recursiveSourcesMap(nodes):
59     rv = {}
60
61     def check(node, rv=rv):
62         for src in node.sources:
63             if not rv.get(src):
64                 rv[src] = True
65                 check(src)
66     
67     for node in nodes:
68         check(node)
69     
70     return rv
71
72 # Replacement BoostUnitTest builder. This build just defers the real
73 # builder call until later
74 def AutoObjectBoostUnitTest(env, target, tests, **kw):
75     target = env.arg2nodes(target)[0]
76     tests = env.arg2nodes(tests)
77     env.Append(_UNIT_TEST_LIST = (target, tests, kw))
78
79 def setup(env):
80     env['BUILDERS']['RealBoostUnitTest'] = env['BUILDERS']['BoostUnitTest']
81     env['BUILDERS']['BoostUnitTest'] = AutoObjectBoostUnitTest
82     env['_UNIT_TEST_LIST'] = []
83     env.Append(EXTRA_LIBS = [ '$BOOSTREGEXLIB', '$BOOSTSIGNALSLIB',
84                        '$BOOSTFSLIB', '$BOOSTSYSTEMLIB', '$BOOSTDATETIMELIB' ])
85
86 # This needs to be called after all build targets have been set
87 # up. This is important since the list of object targets needs to be
88 # complete.
89 def build(env, accept_unknown_tests=False, verbose=False):
90     env = env.Clone(LIBS = [ '$EXTRA_LIBS' ])
91     if env.has_key("only_tests"):
92         only_tests = {}
93         dir = env.Dir(env.GetLaunchDir())
94         for test in env.Split(env['only_tests']):
95             test = dir.File(test)
96             if not test.name.endswith(".test.cc"):
97                 test = test.target_from_source(prefix="", suffix=".test.cc")
98             only_tests[test] = False
99     else:
100         only_tests = {}
101     for target, tests, kw in env['_UNIT_TEST_LIST']:
102         objects = []
103         build = False
104         for test in tests:
105             if test.suffix == env['OBJSUFFIX']:
106                 objects.append(test)
107             else:
108                 if not only_tests or only_tests.has_key(test):
109                     if only_tests : only_tests[test] = True
110                     objects.extend(env.Object(test))
111                     build = True
112                 elif test.name == 'main.test.cc':
113                     objects.extend(env.Object(test))
114         if not build : continue
115
116         # First step: Get all objects which are needed by any of 'objects'
117         objects = list(neededObjects(env,objects))
118         # Get all the source files needed by those objects
119         sources = recursiveSourcesMap(objects)
120         # Now remove any objects which are also sources: When an
121         # object is a source for another object this means, the target
122         # object has been built by CombinedObject. Since the target
123         # object includes all it's source objects, we need to remove
124         # all those source objects from the object list (if any).
125         objects = [ ob for ob in objects if not sources.get(ob) ]
126
127         env.RealBoostUnitTest(target, objects, **kw)
128
129     if verbose and only_tests and not env.GetOption('no_progress'):
130         SCons.Util.display("scons: building tests: " + ", ".join("`%s'" % str(k)
131                                                                  for k,v in only_tests.iteritems()
132                                                                  if v))
133     if not accept_unknown_tests:
134         only_tests = [ k for k,v in only_tests.iteritems() if not v ]
135         if only_tests:
136             raise SCons.Errors.StopError("Unknown unit tests (only_tests): %s." 
137                                          % ", ".join("`%s'" % x for x in only_tests))
138
139 def findSCMChanges(env):
140
141     def scmchanges(dir):
142         if os.popen("cd %s; svnversion" % dir.abspath).read().strip() in ("","exported"):
143             return [ dir.Entry(x)
144                      for x in os.popen("cd %s; git ls-files --modified" 
145                                        % dir.abspath).read().strip().split("\n") ]
146         else:
147             return [ dir.Entry(l[7:])
148                      for l in os.popen("cd %s; svn status" 
149                                        % dir.abspath).read().rstrip().split("\n")
150                      if l[0] == 'M' ]
151
152     changes=scmchanges(env.Dir('#'))
153     for dir in env.Dir('senf/Ext').glob("*"):
154         if isinstance(dir,SCons.Node.FS.Dir):
155             changes.extend(scmchanges(dir))
156     return [ x for x in changes if not isinstance(x,SCons.Node.FS.Dir) ]