SENF SCons build utility (senfutil.py)

\c senfutil helps setting up projects which utilize SENF. It will configure all necessary
compiler and linker options and additionally sets up some useful defaults and utilities.

\c senfutil really serves three roles

\li detect the SENF library and configure the build accordingly
\li make some SCons extensions used within SENF available to other projects
\li set default compilation options in the same way, they are set when compiling SENF proper.

The last two points are of course optional.

Tutorial

To utilize \c senfutil you need to do two things:
\li Update your \c SConstruct file
\li add a bootstrap \c senfutil.py to \c site_scons

Lets start with the \c SConstruct file
import senfutil
env = Environment()
senfutil.SetupForSENF(env)
senfutil.DefaultOptions(env)
env.SetDefault(
PROJECTNAME = 'Example project',
PROJECTEMAIL = 'developer@domain.com',
DOCLINKS = [ ('Homepage', 'http://www.domain.com') ]
)
sources, tests = senfutil.Glob(env, exclude=['main.cc'])
objects = env.Object(sources)
example = env.Program('example', objects + ['main.cc'])
test = env.BoostUnitTest('test', objects + tests)
env.Default(example)
senfutil.Doxygen(env)
senfutil.CleanGlob('all', [ '*~', '#*#' ])
This simple sample already enables a lot of functionality:
\li support for different \e SENF flavors (debug/normal/final)
\li support for different \e build flavors (debug/normal/final)
\li sensible default compile options for the different flavors
\li support for extended command-line variables
\li building documentation with an auto-generated Doxyfile
\li running unit-tests
\li cleaning backup and temporary files

Here a very quick rundown of the important scons commands:
\li Build default target:
$ scons
  • Build documentation and unit-tests:
    $ scons doc test
    
  • clean up everything
    $ scons -c all
    
  • Pass custom options on the command-line
    $ scons CXXFLAGS+=-Wextra
    
    Since \c senfutil.py is not on the standard \c python or \c SCons path, some extra steps are
    needed to find it.
    \li Either add the possible directories to <tt>sys.path</tt> before importing \c senfutil:
    
    import sys
    sys.path.extend(('/usr/local/lib/senf/site_scons', '/usr/lib/senf/site_scons'))
    import senfutil
  • Alternatively, install the following utility script as site_scons/senfutil.py into your project. This script will search for site_scons/senfutil.py in a list of directories and then load the real senfutil.py on top of itself. The directories searched include: the current directory and all parents, subdirectories named senf/, Senf/ or SENF/ thereof, and /usr/local/lib/senf/ and /usr/lib/senf/
    #!/usr/bin/python
    #
    # Copyright (c) 2020 Fraunhofer Institute for Applied Information Technology (FIT)
    # Network Research Group (NET)
    # Schloss Birlinghoven, 53754 Sankt Augustin, GERMANY
    # Contact: support@wiback.org
    #
    # This file is part of the SENF code tree.
    # It is licensed under the 3-clause BSD License (aka New BSD License).
    # See LICENSE.txt in the top level directory for details or visit
    # https://opensource.org/licenses/BSD-3-Clause
    #
    def findsenf(sysdirs = ('/usr/local/lib/senf','/usr/lib/senf'),
    subdirs = ('', 'senf', 'Senf', 'SENF')):
    import os, itertools
    def ancestors(d):
    p = d.split(os.path.sep)
    return (os.path.join(p[0]+os.path.sep,*p[1:i]) for i in range(len(p),0,-1))
    for d in itertools.chain(ancestors(os.getcwd()),sysdirs):
    for s in subdirs:
    py = os.path.join(d,s,'site_scons/senfutil.py')
    if os.path.exists(py) and not os.path.samefile(py,__file__): return py
    raise ImportError, 'senfutil not found'
    # replace module with real senfutil
    import sys, os
    __file__ = findsenf()
    sys.path.append(os.path.dirname(__file__))
    del sys, os, findsenf
    execfile(__file__, locals(), globals())

senfutil_features

The \c senfutil utility for SCons helps setting up a project to compile against SENF:

\li \c senfutil adds all necessary libraries to link against
\li \c senfutil will set necessary compile options.
\li \c senfutil supports normal, debug and final project build options
\li \c senfutil adds support for Boost unit tests
\li \c senfutil implements a very simple to use enhanced doxygen build with SENF symbol
    cross-reference
\li \c senfutil allows specifying variables on the scons command line
\li \c senfutil supports more readable compile-time SENF loglevel configuration

Using the utility is quite simple
import sys
sys.path.extend(('senf/site_scons','/usr/lib/senf/site_scons'))
import glob, senfutil
env = Environment()
senfutil.SetupForSENF(env)
# senfutil.DefaultOptions(env)
# Set or change SCons environment variables with env.Append, env.Replace or env.SetDefault
env.Append(
CXXFLAGS = [ '-Wall', '-Woverloaded-virtual' ],
CXXFLAGS_final = [ '-O2' ],
CXXFLAGS_normal = [ '-O0', '-g' ],
CXXFLAGS_debug = [ '$CXXFLAGS_normal' ],
LINKFLAGS_normal = [ '-Wl,-S' ],
LOGLEVELS_debug = [ 'senf::log::Debug||VERBOSE' ],
PROJECTNAME = 'Example project',
PROJECTEMAIL = 'developer@domain.com',
DOCLINKS = [ ('Homepage', 'http://www.domain.com') ]
)
# Create a list of sources and tests. Sources are all *.cc files, test are *.test.cc
sources, tests = senfutil.Glob(env, exclude=['main.cc'] )
# Build objects from sources
objects = env.Object(sources)
# Build main binary
env.Default( env.Program( target='example', source=objects + ['main.cc'] ) )
# Build a boost unit-test from additional test sources
env.BoostUnitTest( 'test', source=objects + tests)
# Build a documentation, autogenerates a Doxyfile
senfutil.Doxygen(env)
This example builds a simple binary from a number of source files (all '.cc' files). It links
against the SENF library and automatically sets all the correct compiler options using
<tt>senfutil.SetupForSENF( env )</tt>.

This script automatically uses a SENF installation either symlinked or imported into the current
project in directory 'senf' or, if this directory does not exist, a globally installed SENF.

Build options

\c senfutil supports the <tt>debug=1</tt> or <tt>final=1</tt> build options. These parameters
select one of the build configurations 'debug', 'normal' or 'final'. The following variables are
supported each with separate values for all three configurations:

\li \c CXXFLAGS
\li \c CPPDEFINES
\li \c LINKFLAGS
\li \c LOGLEVELS

\c senfutil will detect the type of SENF library used (final or not) and set the correct compile
options.

Specifying compile-time loglevels

To simplify specifying the compile-time loglevel configuration, the build variable \c LOGLEVELS
(and it's build configuration specific variants) may be set. This variable will be parsed and
converted into the correct \c SENF_LOG_CONF definition. The \c LOGLEVELS Syntax is

\par "" \e optional_stream \c | \e optional_area | \e level

where \e optional_stream and \e optional_area are optional fully scoped C++ names (e.g. \c
senf::log::Debug) and \e level is the loglevel. There must be \e no whitespace in a single
specification, multiple specifications are either specified using an array or separated with
whitespace.

Default options

In the example above, all compile options are set manually. To specify the default customary
compile options for SENF programs, \c senfutil.DefaultOptions(env) is provided. This function is
identical to:
senfutil.DefaultOptions(env) =>
env.Append(
CXXFLAGS = [ '-Wall', '-Woverloaded-virtual' ],
CXXFLAGS_final = [ '-O2' ],
CXXFLAGS_normal = [ '-O0', '-g' ],
CXXFLAGS_debug = [ '$CXXFLAGS_normal' ],
LINKFLAGS_normal = [ '-Wl,-S' ],
)
Thus above example can be simplified to
import sys
sys.path.extend(('senf/site_scons','/usr/lib/senf/site_scons'))
import glob, senfutil
env = Environment()
senfutil.SetupForSENF(env)
senfutil.DefaultOptions(env)
env.Append( LOGLEVELS_debug = [ 'senf::log::Debug||VERBOSE' ],
PROJECTNAME = 'Example project',
PROJECTEMAIL = 'developer@domain.com',
DOCLINKS = [ ('Homepage', 'http://www.domain.com') ] )
sources, tests = senfutil.Glob(env, exclude=['main.cc'] )
objects = env.Object(sources)
env.Default( env.Program( target='example', source=objects + ['main.cc'] ) )
env.BoostUnitTest( 'test', source=objects + tests)
senfutil.Doxygen(env)

Building unit tests

Building unit tests mostly follows a standard pattern
# Generate list of sources and tests (sources=*.cc, tests=*.test.cc)
extra_sources = ['main.cc']
sources, tests = senfutil.Glob(env, exclude=extra_sources)
# Build object files needed for both main target and unit tests
objects = env.Object(sources)
# Build main target, e.g. a Binary with additional sources which are not part of the unit test
env.Program('example', objects+extra_sources)
# Build unit tests including additional test sources
env.BoostUnitTest('test', objects+tests)
It is important to exclude the \c main function from the unit-test build since the boost unit
test library provides it's own.

Building documentation

Documentation is built using the \c senfutil.Doxygen utility
env.Append( PROJECTNAME = "Example project",
PROJECTEMAIL = "coder@example.com",
COPYRIGHT = "The Master Coders",
DOCLINKS = [ ('Homeage', 'http://www.example.com') ],
REVISION = 'r1000' )
senfutil.Doxygen(env)
The \c senfutil.Doxygen utility autogenerates a \c Doxyfile.

The utility will search for a SENF documentation in the \c senfdoc and \c %senf subdirectories
as well as via the senfutil module directory and some other standard locations. If SENF
documentation is found, the SENF tagfiles will automatically be added. Links will be resolved
to the documentation found.

\c senfutil.Doxygen takes some additional optional keyword arguments:
\li \c doxyheader: Path of an alternative HTML header
\li \c doxyfooter: Path of an alternative HTML footer
\li \c doxycss: Path on an alternative CSS file
\li \c mydoxyfile: If set to \c True, don't generate or clean the \c Doxyfile\
\li \c senfdoc_path: List of additional directories to search for SENF documentation

'scons' Command line arguments

\c senfutil automatically parses SCons command line arguments into the SCons build
environment. This allows specifying any parameter on the command line:
$ scons CXX=myg++ CXXFLAGS+=-mtune=geode

You may either set variables unconditionally using '=' or append values to the end of a list using '+='.