Add sparse_tests and only_tests build feature
g0dil [Thu, 8 Oct 2009 19:04:47 +0000 (19:04 +0000)]
git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@1482 270642c3-0616-0410-b53a-bc976706d245

SConstruct
site_scons/SENFSCons.py
site_scons/SparseTestHack.py [new file with mode: 0644]

index d8c0510..97ef138 100644 (file)
@@ -49,8 +49,9 @@ env.Append(
    CPPPATH                = [ '$BUILDDIR', '#' ],
    LOCALLIBDIR            = '$BUILDDIR',
    LIBPATH                = [ '$LOCALLIBDIR' ],
-   LIBS                   = [ '$LIBSENF$LIBADDSUFFIX', 'rt', '$BOOSTREGEXLIB', 
-                              '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', '$BOOSTFSLIB' ], 
+   LIBS                   = [ '$LIBSENF$LIBADDSUFFIX', '$EXTRA_LIBS' ],
+   EXTRA_LIBS             = [ 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', 
+                              '$BOOSTFSLIB' ], 
    TEST_EXTRA_LIBS        = [  ],
    VALGRINDARGS           = [ '--num-callers=50' ],
 
@@ -115,8 +116,10 @@ senfutil.parseArguments(
     BoolVariable('final', 'Build final (optimized) build', False),
     BoolVariable('debug', 'Link in debug symbols', False),
     BoolVariable('syslayout', 'Install in to system layout directories (lib/, include/ etc)', False),
+    BoolVariable('sparse_tests', 'Link tests against object files and not the senf lib', False)
 )
 
+if env.has_key('only_tests') : env['sparse_tests'] = True
 Export('env')
 
 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
@@ -134,6 +137,9 @@ SConscriptChdir(0)
 SConscript("debian/SConscript")
 SConscriptChdir(1)
 if os.path.exists('SConscript.local') : SConscript('SConscript.local')
+if env['sparse_tests']:
+    import SparseTestHack
+    SparseTestHack.setup(env)
 if env.subst('$BUILDDIR') == '#':
     SConscript("SConscript")
 else:
@@ -141,6 +147,8 @@ else:
 SConscript("Examples/SConscript")
 SConscript("HowTos/SConscript")
 SConscript("doclib/SConscript")
+if env['sparse_tests']:
+    SparseTestHack.build(env)
 
 ###########################################################################
 # Define build targets
index f4436b2..13f515e 100644 (file)
@@ -99,9 +99,11 @@ def AllIncludesHH(env, exclude=[]):
                 if f.name not in exclude and not f.name.endswith('.test.hh') ]
     headers.sort(key=lambda x:x.name)
     target = env.File("all_includes.hh")
-    env.Default(env.CreateFile(target, 
-                               env.Value("".join([ '#include <%s>\n' % f.srcnode().get_path(env.Dir('#'))
-                                                   for f in headers ]))))
+    allinch = env.CreateFile(target, 
+                             env.Value("".join([ '#include <%s>\n' % f.srcnode().get_path(env.Dir('#'))
+                                                 for f in headers ])))
+    env.Default(allinch)
+    env.Depends(allinch, headers)
 
 INDEXPAGE="""
 /** \mainpage ${TITLE}
@@ -132,7 +134,6 @@ def IndexPage(env, name, title, text=""):
     env.Clean('all',name)
     env.Clean('all_docs',name)
 
-
 ###########################################################################
 # The following functions serve as simple macros for most SConscript files
 #
diff --git a/site_scons/SparseTestHack.py b/site_scons/SparseTestHack.py
new file mode 100644 (file)
index 0000000..8ae888d
--- /dev/null
@@ -0,0 +1,119 @@
+import SCons.Node, SCons.Node.FS, SCons.Util
+
+#####################################################################
+# This IS a hack .. but a very useful one: The hack will remove the
+# 'senf' library from the unit-tests and instead add all the object
+# files needed explicitly.
+#
+# This works by building a list of all children (recursively) of the
+# test sources. For each child we check, wether a file with the same
+# name but an $OBJSUFFIX extension exists as a build target. In that
+# case, we add it to the list of needed objects (AND recursively scan
+# that objects children).
+#
+# Additionally, of the variable 'only_tests' is set, it contains a
+# list of test source files to be built exclusively: All other test
+# sources will NOT be compiled (and their children will therefore not
+# be scanned).
+#
+# There is one restriction: CompileCheck tests are not run when this
+# hack is enabled.
+#####################################################################
+
+# Build a list of all object file targets which are needed by 'nodes'
+def neededObjects(env,nodes):
+    if not SCons.Util.is_List(nodes) : nodes = [nodes]
+
+    def walk(nodes):
+        for node in nodes:
+            walker = SCons.Node.Walker(node)
+            while True:
+                n = walker.next()
+                if n is None : break
+                yield n
+
+    seen = {}
+    rv = {}
+
+    def check(nodes, env=env, seen=seen, rv=rv):
+        for c in walk(nodes):
+            if isinstance(c,SCons.Node.FS.File) and not seen.get(c):
+                seen[c] = True
+                ob = c.File(env.subst("${SOURCE.dir.abspath}/${OBJPREFIX}${SOURCE.filebase}${OBJSUFFIX}", source=c))
+                if ob.is_derived():
+                    rv[ob] = True
+                    if not seen.get(ob):
+                        seen[ob] = True
+                        check([ob])
+
+    check(nodes)
+
+    for node in nodes:
+        rv[node] = True
+    rv = [ (str(x),x) for x in rv.iterkeys() ]
+    rv.sort()
+    return (x[1] for x in rv)
+
+# Return a map witch has all sources (recursively) of 'nodes' as keys
+def recursiveSourcesMap(nodes):
+    rv = {}
+
+    def check(node, rv=rv):
+        for src in node.sources:
+            if not rv.get(src):
+                rv[src] = True
+                check(src)
+    
+    for node in nodes:
+        check(node)
+    
+    return rv
+
+# Replacement BoostUnitTest builder. This build just defers the real
+# builder call until later
+def AutoObjectBoostUnitTest(env, target, tests, **kw):
+    target = env.arg2nodes(target)[0]
+    tests = env.arg2nodes(tests)
+    env.Append(_UNIT_TEST_LIST = (target, tests, kw))
+
+def setup(env):
+    env['BUILDERS']['RealBoostUnitTest'] = env['BUILDERS']['BoostUnitTest']
+    env['BUILDERS']['BoostUnitTest'] = AutoObjectBoostUnitTest
+    env['_UNIT_TEST_LIST'] = []
+
+# This needs to be called after all build targets have been set
+# up. This is important since the list of object targets needs to be
+# complete.
+def build(env):
+    env = env.Clone(LIBS = [ '$EXTRA_LIBS' ])
+    if env.has_key("only_tests"):
+        only_tests = [ env.Dir(env.GetLaunchDir()).File(test)
+                       for test in env.Split(env['only_tests']) ]
+    else:
+        only_tests = []
+    for target, tests, kw in env['_UNIT_TEST_LIST']:
+        objects = []
+        build = False
+        for test in tests:
+            if test.suffix == env['OBJSUFFIX']:
+                objects.append(test)
+            else:
+                if not only_tests or test in only_tests:
+                    objects.extend(env.Object(test))
+                    build = True
+                elif test.name == 'main.test.cc':
+                    objects.extend(env.Object(test))
+        if not build : continue
+
+        # First step: Get all objects which are needed by any of 'objects'
+        objects = list(neededObjects(env,objects))
+        # Get all the source files needed by those objects
+        sources = recursiveSourcesMap(objects)
+        # Now remove any objects which are also sources: When an
+        # object is a source for another object this means, the target
+        # object has been built by CombinedObject. Since the target
+        # object includes all it's source objects, we need to remove
+        # all those source objects from the object list (if any).
+        objects = [ ob for ob in objects if not sources.get(ob) ]
+
+        env.RealBoostUnitTest(target, objects, **kw)