Fix some build problems
[senf.git] / senfscons / SENFSCons.py
index 623c9d7..812c30e 100644 (file)
@@ -25,6 +25,7 @@
 import os.path, glob
 import SCons.Options, SCons.Environment, SCons.Script.SConscript, SCons.Node.FS
 import SCons.Defaults, SCons.Action
+from SCons.Script import *
 
 ## \defgroup use Predefined Framework Configurators
 #
@@ -51,6 +52,8 @@ SCONS_TOOLS = [
     "Dia2Png",
     "CopyToDir",
     "InstallIncludes",
+    "ProgramNoScan",
+    "CompileCheck",
 ]
 
 opts = None
@@ -69,6 +72,8 @@ def InitOpts():
     opts.Add('EXTRA_DEFINES', 'Additional preprocessor defines', '')
     opts.Add('EXTRA_LIBS', 'Additional libraries to link against', '')
     opts.Add(SCons.Options.BoolOption('final','Enable optimization',0))
+    opts.Add(SCons.Options.BoolOption('debug','Enable debug symbols in binaries',0))
+    opts.Add(SCons.Options.BoolOption('profile','Enable profiling',0))
     opts.Add('PREFIX', 'Installation prefix', '/usr/local')
     opts.Add('LIBINSTALLDIR', 'Library install dir', '$PREFIX/lib')
     opts.Add('BININSTALLDIR', 'Executable install dir', '$PREFIX/bin')
@@ -77,6 +82,8 @@ def InitOpts():
     opts.Add('DOCINSTALLDIR', 'Documentation install dir', '$PREFIX/doc')
     opts.Add('CPP_INCLUDE_EXTENSIONS', 'File extensions to include in source install',
              [ '.h', '.hh', '.ih', '.mpp', '.cci', '.ct', '.cti', '.mpp' ])
+    opts.Add('CPP_EXCLUDE_EXTENSIONS', 'File extensions to exclude from source install',
+             [ '.test.hh' ])
 
 # A finalizer is any callable object. All finalizers will be called
 # in MakeEnvironment. We use them so every finalizer has knowledge of
@@ -132,6 +139,8 @@ def UseBoost():
     opts.Add('BOOST_RUNTIME', 'The boost runtime to use', '')
     opts.Add('BOOST_DEBUG_RUNTIME', 'The boost debug runtime to use', '')
     opts.Add('BOOST_LIBDIR', 'The directory of the boost libraries', '')
+    opts.Add('BOOST_PREFIX', 'The prefix into which boost is installed', '')
+    opts.Add('BOOST_VERSION', 'The version of boost to use', '')
     Finalizer(FinalizeBoost)
 
 ## \brief Finalize Boost environment
@@ -147,12 +156,27 @@ def FinalizeBoost(env):
         if runtime: runtime = "-" + runtime
         env['BOOST_VARIANT'] = "-" + env['BOOST_TOOLSET'] + runtime
 
-    env['BOOSTTESTLIB'] = 'libboost_unit_test_framework' + env['BOOST_VARIANT']
-    env['BOOSTREGEXLIB'] = 'libboost_regex' + env['BOOST_VARIANT']
+    if env['BOOST_VARIANT'] and env['BOOST_VERSION']:
+        env['BOOST_VARIANT'] = env['BOOST_VARIANT'] + '-%s' % env['BOOST_VERSION'].replace('.','_')
+
+    env['BOOSTTESTLIB'] = 'boost_unit_test_framework' + env['BOOST_VARIANT']
+    env['BOOSTREGEXLIB'] = 'boost_regex' + env['BOOST_VARIANT']
+    env['BOOSTFSLIB'] = 'boost_filesystem' + env['BOOST_VARIANT']
+    env['BOOSTIOSTREAMSLIB'] = 'boost_iostreams' + env['BOOST_VARIANT']
+    env['BOOSTSIGNALSLIB'] = 'boost_signals' + env['BOOST_VARIANT']
+
+    if env['BOOST_PREFIX']:
+        env['BOOST_LIBDIR'] = os.path.join(env['BOOST_PREFIX'], 'lib')
+        env['BOOST_INCLUDES'] = os.path.join(env['BOOST_PREFIX'],
+                                             'include/boost-%s'
+                                                 % env['BOOST_VERSION'].replace('.','_'))
 
     env.Append(LIBPATH = [ '$BOOST_LIBDIR' ],
                CPPPATH = [ '$BOOST_INCLUDES' ])
 
+    if env['BOOST_LIBDIR']:
+        env.Append(ENV = { 'LD_LIBRARY_PATH': env['BOOST_LIBDIR'] })
+
 ## \brief Use STLPort as STL replacement if available
 #
 # Use <a href="http://www.stlport.org">STLPort</a> as a replacement
@@ -214,11 +238,12 @@ def MakeEnvironment():
     global opts, finalizers
     InitOpts()
     env = SCons.Environment.Environment(options=opts)
-    for opt in opts.options:
-        if SCons.Script.SConscript.Arguments.get(opt.key):
-            env[opt.key] = SCons.Script.SConscript.Arguments.get(opt.key)
-    if SCons.Script.SConscript.Arguments.get('final'):
-        env['final'] = 1
+    env.Replace(**SCons.Script.SConscript.Arguments)
+    #for opt in opts.options:
+    #    if SCons.Script.SConscript.Arguments.get(opt.key):
+    #        env[opt.key] = SCons.Script.SConscript.Arguments.get(opt.key)
+    #if SCons.Script.SConscript.Arguments.get('final'):
+    #    env['final'] = 1
     env.Help("\nSupported build variables (either in SConfig or on the command line:\n")
     env.Help(opts.GenerateHelpText(env))
 
@@ -236,22 +261,30 @@ def MakeEnvironment():
 
     # These are the default compilation parameters. We should probably
     # make these configurable
-    env.Append(CXXFLAGS = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long' ],
-               LOCALLIBDIR = [ '#' ],
+    env.Append(LOCALLIBDIR = [ '#' ],
                LIBPATH = [ '$LOCALLIBDIR' ])
 
     if env['final']:
-        env.Append(CXXFLAGS = [ '-O3' ],
-                   CPPDEFINES = [ 'NDEBUG' ])
+        env.Append(CXXFLAGS = [ '-O3' ])
+        if env['profile']:
+            env.Append(CXXFLAGS = [ '-g', '-pg' ],
+                       LINKFLAGS = [ '-g', '-pg' ])
     else:
-        env.Append(CXXFLAGS = [ '-O0', '-g', '-fno-inline' ],
-    # The boost-regex library is not compiled with _GLIBCXX_DEBUG so this fails.
-    #               CPPDEFINES = [ '_GLIBCXX_DEBUG' ],
-                   LINKFLAGS = [ '-g' ])
+        # The boost-regex library is not compiled with _GLIBCXX_DEBUG so this fails:
+        #          CPPDEFINES = [ '_GLIBCXX_DEBUG' ],
+        env.Append(CXXFLAGS = [ '-O0', '-g' ],
+                   CPPDEFINES = { 'SENF_DEBUG': ''})
+        if env['profile']:
+            env.Append(CXXFLAGS = [ '-pg' ],
+                       LINKFLAGS = [ '-pg' ])
+        if env['debug'] or env['profile']:
+            env.Append(LINKFLAGS = [ '-g', '-rdynamic' ])
+        else:
+            env.Append(LINKFLAGS = [ '-Wl,-S', '-rdynamic' ])
 
     env.Append(CPPDEFINES = [ '$EXTRA_DEFINES' ],
                LIBS = [ '$EXTRA_LIBS' ],
-               ALLLIBS = [])
+               ALLOBJECTS = [])
 
     return env
 
@@ -295,7 +328,30 @@ def GlobalTargets(env):
 
 ## \brief Return path of a built library within $LOCALLIBDIR
 # \internal
-def LibPath(lib): return '$LOCALLIBDIR/lib%s.a' % lib
+def LibPath(lib): return '${LOCALLIBDIR}/${LIBPREFIX}%s${LIBADDSUFFIX}${LIBSUFFIX}' % lib
+
+## \brief Add explicit test
+#
+# This target helper will add an explicit test. This is like a unit test but is
+# built directly against the completed library
+#
+# \ingroup target
+def Test(env, sources, LIBS = [], OBJECTS = []):
+    test = [ env.BoostUnitTests(
+        target = 'test',
+        objects = [],
+        test_sources = sources,
+        LIBS = [ '$LIBSENF' ],
+        OBJECTS = OBJECTS,
+        DEPENDS = [ env.File(LibPath(env['LIBSENF'])) ]) ]
+    compileTestSources = [ src for src in sources
+                           if 'COMPILE_CHECK' in file(src).read() ]
+    if compileTestSources:
+        test.extend(env.CompileCheck(source = compileTestSources))
+    env.Alias('all_tests', test)
+    env.Command(env.File('test'), test, [])
+    #env.Alias(env.File('test'), test)
+    
 
 ## \brief Build object files
 #
@@ -314,7 +370,7 @@ def LibPath(lib): return '$LOCALLIBDIR/lib%s.a' % lib
 # provide both \a sources and \a testSources.
 #
 # \ingroup target
-def Objects(env, sources, testSources = None, LIBS = [], OBJECTS = []):
+def Objects(env, sources, testSources = None, LIBS = [], OBJECTS = [], no_includes = False):
     if type(sources) == type(()):
         testSources = sources[1]
         sources = sources[0]
@@ -323,38 +379,55 @@ def Objects(env, sources, testSources = None, LIBS = [], OBJECTS = []):
 
     objects = None
     if sources:
-        objects = env.Object([
-            source
-            for source in sources
-            if not str(source).endswith('.o') ]) + [
-            source
-            for source in sources
-            if str(source).endswith('.o') ]
-        
+        obsources = [ source
+                      for source in sources
+                      if type(source) is type('') and not source.endswith('.o') ]
+        objects = [ source
+                    for source in sources
+                    if type(source) is not type('') or source.endswith('.o') ]
+        if obsources:
+            objects += env.Object(obsources)
 
     if testSources:
-        test = env.BoostUnitTests(
+        test = [ env.BoostUnitTests(
             target = 'test',
             objects = objects,
             test_sources = testSources,
-            LIBS = LIBS,
+            LIBS = [ '$LIBSENF' ],
             OBJECTS = OBJECTS,
-            DEPENDS = [ env.File(LibPath(x)) for x in LIBS ])
+            DEPENDS = [ env.File(LibPath(env['LIBSENF'])) ]) ]
+        compileTestSources = [ src for src in testSources
+                               if 'COMPILE_CHECK' in file(src).read() ]
+        if compileTestSources:
+            test.extend(env.CompileCheck(source = compileTestSources))
         env.Alias('all_tests', test)
         # Hmm ... here I'd like to use an Alias instead of a file
         # however the alias does not seem to live in the subdirectory
         # which breaks 'scons -u test'
-        env.Alias(env.File('test'), test)
+        env.Command(env.File('test'), test, [])
+        #env.Alias(env.File('test'), test)
 
     return objects
 
 def InstallIncludeFiles(env, files):
+    # Hrmpf ... why do I need this in 0.97??
+    if env.GetOption('clean'):
+        return
     target = env.Dir(env['INCLUDEINSTALLDIR'])
     base = env.Dir(env['INSTALL_BASE'])
     for f in files:
         src = env.File(f)
         env.Alias('install_all', env.Install(target.Dir(src.dir.get_path(base)), src))
 
+def InstallSourceIncludes(env, sources):
+    target = env.Dir(env['INCLUDEINSTALLDIR']).Dir(
+        env.Dir('.').get_path(env.Dir(env['INSTALL_BASE'])))
+    install = env.InstallIncludes( target = target,
+                                   source = [ type(x) is str and env.File(x) or x
+                                              for x in sources ],
+                                   INSTALL_BASE = env.Dir('.') )
+    env.Alias( 'install_all', install )
+
 def InstallWithSources(env, targets, dir, sources, testSources = [], no_includes = False):
     if type(sources) is type(()):
         sources, testSources = sources
@@ -363,19 +436,15 @@ def InstallWithSources(env, targets, dir, sources, testSources = [], no_includes
     if type(testSources) is not type([]):
         testSources = [ testSources ]
 
-    installs = []
-    installs.append( env.Install(dir, targets) )
+    installs = [ env.Install(dir, targets) ]
+    env.Alias( 'install_all', installs[:] )
 
     if not no_includes:
-        target = env.Dir(env['INCLUDEINSTALLDIR']).Dir(
-            env.Dir('.').get_path(env.Dir(env['INSTALL_BASE'])))
-        source = targets
+        sources = targets
         if testSources:
-            source.append( env.File('.test.bin') )
-            installs.append(env.InstallIncludes(
-                target = target,
-                source = targets,
-                INSTALL_BASE = env.Dir('.') ))
+            sources.append( env.File('.test.bin') )
+        installs.append(
+            InstallSourceIncludes(env, sources))
 
     return installs
 
@@ -417,6 +486,8 @@ def InstallWithSources(env, targets, dir, sources, testSources = [], no_includes
 #
 # \ingroup target
 def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
+    if not 'all' in BUILD_TARGETS and not 'doc' in BUILD_TARGETS and not 'all_docs' in BUILD_TARGETS:
+        return []
     # ARGHHH !!! without the [:] we are changing the target list
     #        ||| WITHIN THE DOXYGEN BUILDER
     docs = env.Doxygen(doxyfile)[:]
@@ -450,13 +521,12 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
             docs,
             SCons.Action.Action(("for html in %s/*.html; do " +
                         "    echo $$html;" +
-                        "    sed -e 's/id=\"current\"/class=\"current\"/' $${html}" +
-                        "        | tidy -ascii -q --show-warnings no --fix-uri no " +
-                        "        | xsltproc --nonet --html --stringparam topdir %s -o $${html}.new %s - 2>&1" +
-                        "        | grep '^-'" +
-                        "        | grep -v 'ID .* already defined';" +
-                        "    mv $${html}.new $${html}; " +
-                        "done")
+                        "    mv $${html} $${html}.orig;" +
+                        "    sed -e 's/id=\"current\"/class=\"current\"/' $${html}.orig" +
+                        "        | tidy -ascii -q --wrap 0 --show-warnings no --fix-uri no " +
+                        "        | sed -e 's/name=\"\([^\"]*\)\"\([^>]*\) id=\"\\1\"/name=\"\\1\"\\2/g'" +
+                        "        | xsltproc --novalid --nonet --html --stringparam topdir %s -o $${html} %s -;"
+                        "done; true")
                        % (htmlnode.dir.abspath, reltopdir, xslfile.abspath)))
         for doc in docs:
             env.Depends(doc, xslfile)
@@ -466,7 +536,7 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
         for type in env.get("DOXY_XREF_TYPES",[ "bug", "todo" ]):
             xref = os.path.join(xmlnode.dir.abspath,type+".xml")
             xref_pp = env.Command(xref+'i', [ xref, os.path.join(basedir,'xrefxtract.xslt'), xmlnode ],
-                                  [ "test -s $SOURCE && xsltproc -o $TARGET" +
+                                  [ "test -s $SOURCE && xsltproc --nonet -o $TARGET" +
                                     " --stringparam module $MODULE" +
                                     " --stringparam type $TYPE" +
                                     " ${SOURCES[1]} $SOURCE || touch $TARGET" ],
@@ -500,8 +570,8 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
     l = len(env.Dir('#').abspath)
     if htmlnode:
         env.Alias('install_all',
-                  env.Install( '$DOCINSTALLDIR' + htmlnode.dir.dir.abspath[l:],
-                               htmlnode.dir ))
+                  env.Command('$DOCINSTALLDIR' + htmlnode.dir.abspath[l:], htmlnode.dir,
+                              [ SCons.Defaults.Copy('$TARGET','$SOURCE') ]))
     if tagnode:
         env.Alias('install_all',
                   env.Install( '$DOCINSTALLDIR' + tagnode.dir.abspath[l:],
@@ -582,26 +652,20 @@ def DoxyXRef(env, docs=None,
 #\ingroup target
 def Lib(env, library, sources, testSources = None, LIBS = [], OBJECTS = [], no_includes = False):
     objects = Objects(env,sources,testSources,LIBS=LIBS,OBJECTS=OBJECTS)
-    lib = None
     if objects:
-        lib = env.Library(env.File(LibPath(library)),objects)
-        env.Default(lib)
-        env.Append(ALLLIBS = library)
-        env.Alias('default', lib)
-        install = InstallWithSources(env, lib, '$LIBINSTALLDIR', sources, testSources, no_includes)
-        env.Alias('install_all', install)
-    return lib
+        env.Append(ALLOBJECTS = objects)
+        InstallSourceIncludes(env, sources)
+    return objects
 
 ## \brief Build Object from multiple sources
 def Object(env, target, sources, testSources = None, LIBS = [], OBJECTS = [], no_includes = False):
     objects = Objects(env,sources,testSources,LIBS=LIBS,OBJECTS=OBJECTS)
     ob = None
     if objects:
-        ob = env.Command(target+".o", objects, "ld -r -o $TARGET $SOURCES")
+        ob = env.Command(target+"${OBJADDSUFFIX}${OBJSUFFIX}", objects, "ld -r -o $TARGET $SOURCES")
         env.Default(ob)
         env.Alias('default', ob)
-        install = InstallWithSources(env, ob, '$OBJINSTALLDIR', sources, testSources, no_includes)
-        env.Alias('install_all', install)
+        InstallSourceIncludes(env, sources)
     return ob
 
 ## \brief Build executable
@@ -618,21 +682,18 @@ def Binary(env, binary, sources, testSources = None, LIBS = [], OBJECTS = [], no
     objects = Objects(env,sources,testSources,LIBS=LIBS,OBJECTS=OBJECTS)
     program = None
     if objects:
-        progEnv = env.Copy()
-        progEnv.Prepend(LIBS = LIBS)
-        program = progEnv.Program(target=binary,source=objects+OBJECTS)
+        progEnv = env.Clone()
+        progEnv.Prepend(LIBS = [ '$LIBSENF' ])
+        program = progEnv.ProgramNoScan(target=binary,source=objects+OBJECTS)
         env.Default(program)
-        env.Depends(program, [ env.File(LibPath(x)) for x in LIBS ])
+        env.Depends(program, [ env.File(LibPath(env['LIBSENF'])) ])
         env.Alias('default', program)
-        install = InstallWithSources(env, program, '$BININSTALLDIR', sources, testSources,
-                                     no_includes)
-        env.Alias('install_all', install)
+        InstallWithSources(env, program, '$BININSTALLDIR', sources, testSources, no_includes)
     return program
 
 def AllIncludesHH(env, headers):
     headers.sort()
-    file(env.File("all_includes.hh").abspath,"w").write("".join([ '#include "%s"\n' % f
-                                                                       for f in headers ]))
-    env.Alias('all', 'all_includes.hh')
-    env.Clean('all', 'all_includes.hh')
-    
+    target = env.File("all_includes.hh")
+    file(target.abspath,"w").write("".join([ '#include "%s"\n' % f
+                                             for f in headers ]))
+    env.Clean('all', target)