Further top-dir cleanup and lcov target
[senf.git] / SConstruct
index 9870f20..cabb4bc 100644 (file)
 # -*- python -*-
 
-import sys, glob, os.path, datetime, pwd, time, fnmatch
-sys.path.append('senfscons')
-import SENFSCons
+import sys, glob, os.path, fnmatch
+import SENFSCons, senfutil
 
 ###########################################################################
+# Load utilities and setup libraries and configure build
 
-# This hack is needed for SCons V 0.96.1 compatibility. In current SCons versions
-# we can just use 'env.AlwaysBuild(env.Alias(target), [], action)'
-def PhonyTarget(env, target, action):
-    env.AlwaysBuild(env.Command(target + '.phony', 'SConstruct', env.Action(action)))
-    env.Alias(target, target + '.phony')
-
-def updateRevision(target, source, env):
-    rev = env['ENV']['REVISION'][1:]
-    if ':' in rev:
-        print
-        print "Working copy not clean. Run 'svn update'"
-        print
-        return 1
-    if 'm' in rev and not ARGUMENTS.get('force_deb'):
-        print
-        print "Working copy contains local changes. Commit first"
-        print
-        return 1
-    if 's' in rev:
-        rev = rev[:-1]
-    if 'm' in rev:
-        rev = rev[:-1]
-    changelog = file('debian/changelog.template').read() % {
-        'rev': rev,
-        'user': pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0].strip(),
-        'date': time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) }
-    file('debian/changelog','w').write(changelog)
-
-def nonemptyFile(f):
-    try: return os.stat(f).st_size > 0
-    except OSError: return False
-
-def checkLocalConf(target, source, env):
-    if [ True for f in env['CONFIG_FILES'] if nonemptyFile(f) ]:
-        print
-        print "You have made local modifications to one of the following local configuration"
-        print "files:"
-        for f in env['CONFIG_FILES']:
-            print "    ",f
-        print
-        print "Building a debian package would remove those files."
-        print
-        print "To continue, remove the offending file(s) and try again. Alternatively,"
-        print "build a source package using 'scons debsrc' and may then build debian"
-        print "binary packages from this source-package without disrupting your local"
-        print "configuration."
-        print
-        return 1
+env = Environment()
 
-###########################################################################
-# Load utilities and setup libraries and configure build
+env.Decider('MD5-timestamp')
+env.EnsureSConsVersion(1,2)
 
-SENFSCons.UseBoost()
-SENFSCons.UseSTLPort()
-env = SENFSCons.MakeEnvironment()
+# Load all the local SCons tools
+env.Tool('Doxygen')
+env.Tool('Dia2Png')
+env.Tool('PkgDraw')
+env.Tool('InstallSubdir')
+env.Tool('CopyToDir')
+env.Tool('Boost')
+env.Tool('CombinedObject')
+env.Tool('PhonyTarget')
+env.Tool('InstallDir')
 
 env.Help("""
 Additional top-level build targets:
 
-prepare      Create all source files not part of the repository
+prepare      Create all target files not part of the repository
+default      Build all default targets (like calling scons with no arguments)
+examples     Build all examples
 all_tests    Build and run unit tests for all modules
 all_docs     Build documentation for all modules
 all          Build everything
-install_all  Install SENF into $PREFIX
+install_all  Install SENF into $$PREFIX
 deb          Build debian source and binary package
 debsrc       Build debian source package
 debbin       Build debian binary package
 linklint     Check links of doxygen documentation with 'linklint'
 fixlinks     Fix broken links in doxygen documentation
+valgrind     Run all tests under valgrind/memcheck
+lcov         Generate test coverage output in doc/lcov and lcov.info
 """)
 
-if os.environ.get('debian_build'):
-    rev = os.popen("dpkg-parsechangelog | awk '/^Version:/{print $2}'").read().strip()
-else:
-    rev = 'r' + os.popen("svnversion").read().strip().lower()
-
-logname = os.environ.get('LOGNAME')
-if not logname:
-    logname = pwd.getpwuid(os.getuid()).pw_name
+env.Append(
+   ENV                    = { 'PATH' : os.environ.get('PATH'), 'HOME' : os.environ.get('HOME') },
+   CLEAN_PATTERNS         = [ '*~', '#*#', '*.pyc', 'semantic.cache', '.sconsign*',
+                              '*.gcno', '*.gcda', '*.gcov' ],
+
+   CPPPATH                = [ '#' ],
+   LOCALLIBDIR            = '#',
+   LIBPATH                = [ '$LOCALLIBDIR' ],
+   LIBS                   = [ '$LIBSENF$LIBADDSUFFIX', 'rt', '$BOOSTREGEXLIB', 
+                              '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB', '$BOOSTFSLIB' ], 
+   TEST_EXTRA_LIBS        = [  ],
+
+   PREFIX                 = '#/dist',
+   LIBINSTALLDIR          = '$PREFIX${syslayout and "/lib" or ""}',
+   BININSTALLDIR          = '$PREFIX${syslayout and "/bin" or ""}',
+   INCLUDEINSTALLDIR      = '$PREFIX${syslayout and "/include" or ""}',
+   CONFINSTALLDIR         = '${syslayout and "$LIBINSTALLDIR/senf" or "$PREFIX"}',
+   OBJINSTALLDIR          = '$CONFINSTALLDIR',
+   DOCINSTALLDIR          = '$PREFIX${syslayout and "/share/doc/senf" or "/manual"}',
+   SCONSINSTALLDIR        = '$CONFINSTALLDIR/site_scons',
+
+   CPP_INCLUDE_EXTENSIONS = [ '.h', '.hh', '.ih', '.mpp', '.cci', '.ct', '.cti' ],
+   CPP_EXCLUDE_EXTENSIONS = [ '.test.hh' ],
+
+   # These options are insane. Only useful for inline debugging. Need at least 1G free RAM
+   INLINE_OPTS_DEBUG      = [ '-finline-limit=20000', '-fvisibility-inlines-hidden', 
+                              '-fno-inline-functions', '-Winline' 
+                              '--param','large-function-growth=10000',
+                              '--param', 'large-function-insns=10000', 
+                              '--param','inline-unit-growth=10000' ],
+   INLINE_OPTS_NORMAL     = [ '-finline-limit=5000' ],
+   INLINE_OPTS            = [ '$INLINE_OPTS_NORMAL' ],
+   CXXFLAGS               = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long', '$INLINE_OPTS',
+                              '$CXXFLAGS_' ],
+   CXXFLAGS_              = senfutil.BuildTypeOptions('CXXFLAGS'),
+   CXXFLAGS_final         = [ '-O3' ],
+   CXXFLAGS_normal        = [ '-O0', '-g' ],
+   CXXFLAGS_debug         = [ '$CXXFLAGS_normal' ],
+
+   CPPDEFINES             = [ '$expandLogOption', '$CPPDEFINES_' ],
+   expandLogOption        = senfutil.expandLogOption,
+   CPPDEFINES_            = senfutil.BuildTypeOptions('CPPDEFINES'),
+   CPPDEFINES_final       = [ ],
+   CPPDEFINES_normal      = [ 'SENF_DEBUG' ],
+   CPPDEFINES_debug       = [ '$CPPDEFINES_normal' ],
+
+   LINKFLAGS              = [ '-rdynamic', '$LINKFLAGS_' ],
+   LINKFLAGS_             = senfutil.BuildTypeOptions('LINKFLAGS'),
+   LINKFLAGS_final        = [ ],
+   LINKFLAGS_normal       = [ '-Wl,-S' ],
+   LINKFLAGS_debug        = [ '-g' ],
+)
 
-def configFilesOpts(target, source, env, for_signature):
-    return [ '-I%s' % os.path.split(f)[1] for f in env['CONFIG_FILES'] ]
+env.SetDefault(
+    LIBSENF           = "senf",
+    LCOV              = "lcov",
+    GENHTML           = "genhtml",
+    SCONS             = "./tools/scons -j$CONCURRENCY_LEVEL",
+    CONCURRENCY_LEVEL = env.GetOption('num_jobs') or 1,
+)
 
-env.Append(
-   CPPPATH = [ '#/include' ],
-   LIBS = [ 'iberty', '$BOOSTREGEXLIB', '$BOOSTFSLIB' ],
-   DOXY_XREF_TYPES = [ 'bug', 'fixme', 'todo', 'idea' ],
-   DOXY_HTML_XSL = '#/doclib/html-munge.xsl',
-   ENV = { 'TODAY' : str(datetime.date.today()),
-           'REVISION' : rev,
-           'LOGNAME' : logname, # needed by the debian build scripts
-           'CONCURRENCY_LEVEL' : env.GetOption('num_jobs') or "1",
-           'SCONS' : 1
-           },
-   CONFIG_FILES = [ 'Doxyfile.local', 'SConfig', 'local_config.hh' ],
-   CONFIG_FILES_OPTS = configFilesOpts,
-   CLEAN_PATTERNS = [ '*.pyc', 'semantic.cache', '.sconsign', '.sconsign.dblite' ],
-   BUILDPACKAGE_COMMAND = "dpkg-buildpackage -us -uc -rfakeroot -I.svn $CONFIG_FILES_OPTS",
-   TOP_INCLUDES = [ 'Packets', 'PPI', 'Scheduler', 'Socket', 'Utils',
-                    'config.hh', 'local_config.hh' ]
+# Set variables from command line
+senfutil.parseArguments(
+    env,
+    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),
 )
 
 Export('env')
 
 # Create Doxyfile.local otherwise doxygen will barf on this non-existent file
-if not env.GetOption('clean') and not os.path.exists("Doxyfile.local"):
+# Create it even when cleaning, to silence the doxygen builder warnings
+if not os.path.exists("Doxyfile.local"):
     Execute(Touch("Doxyfile.local"))
 
-# Create local_config.h
-if not env.GetOption('clean') and not os.path.exists("local_config.hh"):
-    Execute(Touch("local_config.hh"))
-
-###########################################################################
-# Define build targets
-
-# Before defining any targets, check wether this is the first build in
-# pristine directory tree. If so, call 'scons prepare' so the dependencies
-# created later are correct
-
 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp") \
-   and not os.environ.get("SCONS"):
+   and not os.environ.get("SCONS") and COMMAND_LINE_TARGETS != [ 'prepare' ]:
     env.Execute([ "scons prepare" ])
 
-env.Clean('all', '.prepare-stamp')
+# Load SConscripts
 
-SConscript(glob.glob("*/SConscript"))
+SConscriptChdir(0)
+SConscript("debian/SConscript")
+SConscriptChdir(1)
+if os.path.exists('SConscript.local') : SConscript('SConscript.local')
+SConscript("senf/SConscript")
+SConscript("Examples/SConscript")
+SConscript("HowTos/SConscript")
+SConscript("doclib/SConscript")
 
-SENFSCons.StandardTargets(env)
-SENFSCons.GlobalTargets(env)
-SENFSCons.Doxygen(env)
-SENFSCons.DoxyXRef(env,
-                   HTML_HEADER = '#/doclib/doxy-header-overview.html',
-                   HTML_FOOTER = '#/doclib/doxy-footer.html')
+###########################################################################
+# Define build targets
 
-SENFSCons.InstallIncludeFiles(env, [ 'config.hh' ])
+#### doc
+env.Depends(SENFSCons.Doxygen(env), env.Value(env['ENV']['REVISION']))
 
-# Build combined library 'libsenf'
-libsenf = env.Library(
-    SENFSCons.LibPath('senf'),
-    Flatten([ env.File(SENFSCons.LibPath(lib)).sources for lib in env['ALLLIBS'] ]))
+#### libsenf.a
+libsenf = env.Library("$LOCALLIBDIR/${LIBSENF}${LIBADDSUFFIX}", env['ALLOBJECTS'])
 env.Default(libsenf)
-env.Clean('all', 'libsenf.a')
-env.Alias('default', 'libsenf.a')
-
-env.Alias('install_all', env.Install('$LIBINSTALLDIR', libsenf))
-
-env.Clean('all', [ os.path.join(path,f)
-                   for path, subdirs, files in os.walk('.')
-                   for pattern in env['CLEAN_PATTERNS']
-                   for f in fnmatch.filter(files,pattern) ])
-
-PhonyTarget(env, 'deb', [
-    checkLocalConf,
-    updateRevision,
-    "$BUILDPACKAGE_COMMAND",
-])
-
-PhonyTarget(env, 'debsrc', [
-    updateRevision,
-    "$BUILDPACKAGE_COMMAND -S",
-])
-
-PhonyTarget(env, 'debbin', [
-    checkLocalConf,
-    updateRevision,
-    "$BUILDPACKAGE_COMMAND -nc",
-])
-
-PhonyTarget(env, 'linklint', [
-    'rm -rf linklint',
-    'linklint -doc linklint -limit 99999999 `find -type d -name html -printf "/%P/@ "`',
-    '[ ! -r linklint/errorX.html ] || python linklint_addnames.py <linklint/errorX.html >linklint/errorX.html.new',
-    '[ ! -r linklint/errorX.html.new ] || mv linklint/errorX.html.new linklint/errorX.html',
-    '[ ! -r linklint/errorAX.html ] || python linklint_addnames.py <linklint/errorAX.html >linklint/errorAX.html.new',
-    '[ ! -r linklint/errorAX.html.new ] || mv linklint/errorAX.html.new linklint/errorAX.html',
-    'echo -e "\\nLokal link check results: linklint/index.html\\nRemote link check results: linklint/urlindex.html\\n"',
-])
-
-PhonyTarget(env, 'fixlinks', [
-    'python doclib/fix-links.py -v -s .svn -s linklint -s debian linklint/errorX.txt linklint/errorAX.txt',
-])
-
-PhonyTarget(env, 'prepare', [])
-
-env.Clean('all', env.Dir('linklint'))
-
-env.Clean('all','.prepare-stamp')
+env.Install('$LIBINSTALLDIR', libsenf)
+
+def create(target, source, env): 
+    file(str(target[0]), 'w').write(source[0].get_contents()+"\n")
+env['BUILDERS']['CreateFile'] = Builder(action = create)
+
+conf = env.CreateFile("${LOCALLIBDIR}/${LIBSENF}${LIBADDSUFFIX}.conf", 
+                      env.Value(env.subst("$_CPPDEFFLAGS")))
+env.Default(conf)
+env.Install('$CONFINSTALLDIR', conf)
+
+#### install_all, default, all_tests, all
+env.Install('${SCONSINSTALLDIR}', [ 'site_scons/__init__.py',
+                                    'site_scons/senfutil.py',
+                                    'site_scons/yaptu.py' ])
+env.InstallDir('${SCONSINSTALLDIR}', [ 'site_scons/site_tools', 'site_scons/lib' ],
+               FILTER_SUFFIXES=[ '','.css','.pl','.py','.sh','.sty','.xml','.xsl','.yap' ])
+env.Install('${INCLUDEINSTALLDIR}', 'boost')
+
+env.Alias('install_all', env.FindInstalledFiles())
+env.Alias('default', DEFAULT_TARGETS)
+env.Alias('all_tests', env.FindAllBoostUnitTests())
+env.Alias('all', [ 'default', 'all_tests', 'examples', 'all_docs' ])
+
+#### prepare
+env.PhonyTarget('prepare', [], [])
+
+#### valgrind
+env.PhonyTarget('valgrind', [ 'all_tests' ], [ """
+    find -name .test.bin 
+        | while read test; do
+            echo;
+            echo "Running $$test";
+            echo;
+            valgrind --tool=memcheck --error-exitcode=99 --suppressions=tools/valgrind.sup 
+                $$test $BOOSTTESTARGS;
+            [ $$? -ne 99 ] || exit 1;
+        done
+""".replace("\n"," ") ])
+
+### lcov
+env.Alias('lcov', env.AlwaysBuild(
+    env.Command( [ env.Dir('doc/lcov'), 'lcov.info' ], [], [
+        '$SCONS debug=1 CCFLAGS+="-fprofile-arcs -ftest-coverage" LIBS+="gcov" all_tests',
+        '$LCOV --directory . --capture --output-file /tmp/senf_lcov.info --base-directory .',
+        '$LCOV --output-file ${TARGETS[1]} --remove /tmp/senf_lcov.info \\*/include/\\*',
+        '$GENHTML --output-directory ${TARGETS[0]} --title all_tests ${TARGETS[1]}',
+        'rm /tmp/senf_lcov.info' ])))
+
+#### clean
+env.Clean('all', '.prepare-stamp')
+env.Clean('all', libsenf)
+env.Clean('all', env.Dir('linklint')) # env.Dir to disambiguate from linklint PhonyTarget
+env.Clean('all', env.Dir('dist'))
+env.Clean('all', 'lcov.info')
+
+if env.GetOption('clean'):
+    env.Clean('all', [ os.path.join(path,f)
+                       for path, subdirs, files in os.walk('.')
+                       for pattern in env['CLEAN_PATTERNS']
+                       for f in fnmatch.filter(files,pattern) ])
+
 if not env.GetOption('clean') and not os.path.exists(".prepare-stamp"):
     Execute(Touch(".prepare-stamp"))