PPI: Rename all 'Reader's to 'Source's and 'Writer's to 'Sink's
[senf.git] / senfscons / SENFSCons.py
index d87f229..40939cb 100644 (file)
@@ -49,6 +49,9 @@ import SCons.Defaults, SCons.Action
 SCONS_TOOLS = [
     "Doxygen",
     "Dia2Png",
+    "CopyToDir",
+    "InstallIncludes",
+    "ProgramNoScan",
 ]
 
 opts = None
@@ -67,6 +70,14 @@ 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('PREFIX', 'Installation prefix', '/usr/local')
+    opts.Add('LIBINSTALLDIR', 'Library install dir', '$PREFIX/lib')
+    opts.Add('BININSTALLDIR', 'Executable install dir', '$PREFIX/bin')
+    opts.Add('INCLUDEINSTALLDIR', 'Include-file install dir', '$PREFIX/include')
+    opts.Add('OBJINSTALLDIR', 'Static object file install dir', '$LIBINSTALLDIR')
+    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' ])
 
 # A finalizer is any callable object. All finalizers will be called
 # in MakeEnvironment. We use them so every finalizer has knowledge of
@@ -108,7 +119,7 @@ def Finalizer(f):
 # libraries are provided by the distribution, you probably don't need
 # to specify any parameters. If your configuration is more complex,
 # refer to the <a
-# href="http://www.boost.org/tools/build/v1/build_system.htm">Boost.Build</a>
+# href="http://www.boost.org/tools/build/v2/index.html">Boost.Build</a>
 # documentation for a definition of the terms used above (toolset,
 # variant, runtime ...).
 #
@@ -138,6 +149,7 @@ def FinalizeBoost(env):
         env['BOOST_VARIANT'] = "-" + env['BOOST_TOOLSET'] + runtime
 
     env['BOOSTTESTLIB'] = 'libboost_unit_test_framework' + env['BOOST_VARIANT']
+    env['BOOSTREGEXLIB'] = 'libboost_regex' + env['BOOST_VARIANT']
 
     env.Append(LIBPATH = [ '$BOOST_LIBDIR' ],
                CPPPATH = [ '$BOOST_INCLUDES' ])
@@ -203,8 +215,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.Help("\nSupported build variables (either in SConfig or on the command line:\n")
     env.Help(opts.GenerateHelpText(env))
 
     # We want to pass the SSH_AUTH_SOCK system env-var so we can ssh
@@ -230,10 +246,13 @@ def MakeEnvironment():
                    CPPDEFINES = [ 'NDEBUG' ])
     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' ])
 
     env.Append(CPPDEFINES = [ '$EXTRA_DEFINES' ],
-               LIBS = [ '$EXTRA_LIBS' ])
+               LIBS = [ '$EXTRA_LIBS' ],
+               ALLLIBS = [])
 
     return env
 
@@ -244,9 +263,13 @@ def MakeEnvironment():
 # in the current directory. The sources will be returned as a tuple of
 # sources, test-sources. The target helpers all accept such a tuple as
 # their source argument.
-def GlobSources(exclude=[]):
+def GlobSources(exclude=[], subdirs=[]):
     testSources = glob.glob("*.test.cc")
     sources = [ x for x in glob.glob("*.cc") if x not in testSources and x not in exclude ]
+    for subdir in subdirs:
+        testSources += glob.glob(os.path.join(subdir,"*.test.cc"))
+        sources += [ x for x in glob.glob(os.path.join(subdir,"*.cc"))
+                     if x not in testSources and x not in exclude ]
     return (sources, testSources)
 
 ## \brief Add generic standard targets for every module
@@ -269,7 +292,7 @@ def StandardTargets(env):
 #
 # \ingroup target
 def GlobalTargets(env):
-    env.Depends(env.Alias('all'),'#')
+    env.Alias('all', [ 'default', 'all_tests', 'all_docs' ])
 
 ## \brief Return path of a built library within $LOCALLIBDIR
 # \internal
@@ -292,21 +315,31 @@ 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 = []):
+def Objects(env, sources, testSources = None, LIBS = [], OBJECTS = []):
     if type(sources) == type(()):
         testSources = sources[1]
         sources = sources[0]
+    if type(sources) is not type([]):
+        sources = [ sources ]
 
     objects = None
     if sources:
-        objects = env.Object(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') ]
+        
 
     if testSources:
         test = env.BoostUnitTests(
             target = 'test',
-            source = sources,
-            test_source = testSources,
+            objects = objects,
+            test_sources = testSources,
             LIBS = LIBS,
+            OBJECTS = OBJECTS,
             DEPENDS = [ env.File(LibPath(x)) for x in LIBS ])
         env.Alias('all_tests', test)
         # Hmm ... here I'd like to use an Alias instead of a file
@@ -316,6 +349,36 @@ def Objects(env, sources, testSources = None, LIBS = []):
 
     return objects
 
+def InstallIncludeFiles(env, files):
+    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 InstallWithSources(env, targets, dir, sources, testSources = [], no_includes = False):
+    if type(sources) is type(()):
+        sources, testSources = sources
+    if type(sources) is not type([]):
+        sources = [ sources ]
+    if type(testSources) is not type([]):
+        testSources = [ testSources ]
+
+    installs = [ env.Install(dir, targets) ]
+
+    if not no_includes:
+        target = env.Dir(env['INCLUDEINSTALLDIR']).Dir(
+            env.Dir('.').get_path(env.Dir(env['INSTALL_BASE'])))
+        source = targets
+        if testSources:
+            source.append( env.File('.test.bin') )
+            installs.append(env.InstallIncludes(
+                target = target,
+                source = targets,
+                INSTALL_BASE = env.Dir('.') ))
+
+    return installs
+
 ## \brief Build documentation with doxygen
 #
 # The doxygen target helper will build software documentation using
@@ -342,7 +405,7 @@ def Objects(env, sources, testSources = None, LIBS = []):
 #     generated) by the given XSLT stylesheet. Since the HTML
 #     generated by doxygen is broken, we first filter the code through
 #     HTML-\c tidy and filter out some error messages.
-# \li If xml output is generatedwe create files \c bug.xmli and \c
+# \li If xml output is generated we create files \c bug.xmli and \c
 #     todo.xmli which contain all bugs and todo items specified in the
 #     sources. The format of these files is much more suited to
 #     postprocessing and is a more database like format as the doxygen
@@ -388,7 +451,7 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
             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" +
+                        "        | 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';" +
@@ -396,7 +459,7 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
                         "done")
                        % (htmlnode.dir.abspath, reltopdir, xslfile.abspath)))
         for doc in docs:
-            env.Depends(doc,xslfile)
+            env.Depends(doc, xslfile)
 
     if xmlnode:
         xrefs = []
@@ -407,7 +470,8 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
                                     " --stringparam module $MODULE" +
                                     " --stringparam type $TYPE" +
                                     " ${SOURCES[1]} $SOURCE || touch $TARGET" ],
-                                  MODULE = xmlnode.dir.dir.dir.name,
+                                  MODULE = xmlnode.dir.dir.dir.abspath[
+                                               len(env.Dir('#').abspath)+1:],
                                   TYPE = type)
             env.SideEffect(xref, xmlnode)
             env.AddPreAction(docs, "rm -f %s" % (xref,))
@@ -415,12 +479,34 @@ def Doxygen(env, doxyfile = "Doxyfile", extra_sources = []):
             xrefs.extend(xref_pp)
         docs.extend(xrefs)
 
-    env.Depends(docs,extra_sources)
+    if extra_sources and htmlnode:
+        env.Depends(docs,
+                    [ env.CopyToDir( source=source, target=htmlnode.dir )
+                      for source in extra_sources ])
+
+    if extra_sources and xmlnode:
+        env.Depends(docs,
+                    [ env.CopyToDir( source=source, target=xmlnode.dir )
+                      for source in extra_sources ])
+
+    if not htmlnode and not xmlnode:
+        env.Depends(docs, extra_sources)
+
     for doc in docs :
         env.Alias('all_docs', doc)
         env.Clean('all_docs', doc)
         env.Clean('all', doc)
 
+    l = len(env.Dir('#').abspath)
+    if htmlnode:
+        env.Alias('install_all',
+                  env.Install( '$DOCINSTALLDIR' + htmlnode.dir.dir.abspath[l:],
+                               htmlnode.dir ))
+    if tagnode:
+        env.Alias('install_all',
+                  env.Install( '$DOCINSTALLDIR' + tagnode.dir.abspath[l:],
+                               tagnode ))
+
     return docs
 
 ## \brief Build combined doxygen cross-reference
@@ -439,7 +525,8 @@ def DoxyXRef(env, docs=None,
         docs = env.Alias('all_docs')[0].sources
     xrefs = [ doc for doc in docs if os.path.splitext(doc.name)[1] == ".xmli" ]
     xref = env.Command("doc/html/xref.xml", xrefs,
-                       [ "echo -e '<?xml version=\"1.0\"?>\\n<xref>' >$TARGET",
+                       [ "echo '<?xml version=\"1.0\"?>' > $TARGET",
+                         "echo '<xref>' >> $TARGET",
                          "cat $SOURCES >> $TARGET",
                          "echo '</xref>' >>$TARGET" ])
 
@@ -493,15 +580,30 @@ def DoxyXRef(env, docs=None,
 # The library is added to the list of default targets.
 #
 #\ingroup target
-def Lib(env, library, sources, testSources = None, LIBS = []):
-    objects = Objects(env,sources,testSources,LIBS=LIBS)
+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
 
+## \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")
+        env.Default(ob)
+        env.Alias('default', ob)
+        install = InstallWithSources(env, ob, '$OBJINSTALLDIR', sources, testSources, no_includes)
+        env.Alias('install_all', install)
+    return ob
+
 ## \brief Build executable
 #
 # This target helper will build the given binary.  The \a sources, \a
@@ -512,13 +614,24 @@ def Lib(env, library, sources, testSources = None, LIBS = []):
 # construction environment parameters or the framework helpers.
 #
 # \ingroup target
-def Binary(env, binary, sources, testSources = None, LIBS = []):
-    objects = Objects(env,sources,testSources,LIBS=LIBS)
+def Binary(env, binary, sources, testSources = None, LIBS = [], OBJECTS = [], no_includes = False):
+    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)
+        program = progEnv.ProgramNoScan(target=binary,source=objects+OBJECTS)
         env.Default(program)
         env.Depends(program, [ env.File(LibPath(x)) for x in LIBS ])
+        env.Alias('default', program)
+        install = InstallWithSources(env, program, '$BININSTALLDIR', sources, testSources,
+                                     no_includes)
+        env.Alias('install_all', install)
     return program
+
+def AllIncludesHH(env, headers):
+    headers.sort()
+    target = env.File("all_includes.hh")
+    file(target.abspath,"w").write("".join([ '#include "%s"\n' % f
+                                             for f in headers ]))
+    env.Clean('all', target)