SENF SCons build utility (senfutil.py)

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.

senfutil really serves three roles

  • detect the SENF library and configure the build accordingly
  • make some SCons extensions used within SENF available to other projects
  • set default compilation options in the same way, they are set when compiling SENF proper.
The last two points are of course optional.

1. Tutorial

To utilize senfutil you need to do two things:
  • Update your SConstruct file
  • add a bootstrap senfutil.py to site_scons
Lets start with the 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:

  • support for different SENF flavors (debug/normal/final)
  • support for different build flavors (debug/normal/final)
  • sensible default compile options for the different flavors
  • support for extended command-line variables
  • building documentation with an auto-generated Doxyfile
  • running unit-tests
  • cleaning backup and temporary files
Here a very quick rundown of the important scons commands:
  • 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 senfutil.py is not on the standard python or SCons path, some extra steps are needed to find it.
  • Either add the possible directories to sys.path before importing 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/
    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())
    

2. senfutil_features

The senfutil utility for SCons helps setting up a project to compile against SENF:
  • senfutil adds all necessary libraries to link against
  • senfutil will set necessary compile options.
  • senfutil supports normal, debug and final project build options
  • senfutil adds support for Boost unit tests
  • senfutil implements a very simple to use enhanced doxygen build with SENF symbol cross-reference
  • senfutil allows specifying variables on the scons command line
  • 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 senfutil.SetupForSENF( env ).

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.

3. Build options

senfutil supports the debug=1 or final=1 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:
  • CXXFLAGS
  • CPPDEFINES
  • LINKFLAGS
  • LOGLEVELS
senfutil will detect the type of SENF library used (final or not) and set the correct compile options.

4. Specifying compile-time loglevels

To simplify specifying the compile-time loglevel configuration, the build variable LOGLEVELS (and it's build configuration specific variants) may be set. This variable will be parsed and converted into the correct SENF_LOG_CONF definition. The LOGLEVELS Syntax is
optional_stream | optional_area | level
where optional_stream and optional_area are optional fully scoped C++ names (e.g. senf::log::Debug) and level is the loglevel. There must be no whitespace in a single specification, multiple specifications are either specified using an array or separated with whitespace.

5. Default options

In the example above, all compile options are set manually. To specify the default customary compile options for SENF programs, 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)

6. 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 main function from the unit-test build since the boost unit test library provides it's own.

7. Building documentation

Documentation is built using the senfutil.Doxygen utility
env.Append( PROJECTNAME  = "Example project",
            PROJECTEMAIL = "coder@example.com",
            COPYRIGHT    = "The Master Coders",
            DOCLINKS     = [ ('Homeage', 'http://www.example.com') ],
            REVISION     = 'r'+os.popen('svnversion').read().strip().lower() )

senfutil.Doxygen(env)

The senfutil.Doxygen utility autogenerates a Doxyfile.

The utility will search for a SENF documentation in the senfdoc and 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.

senfutil.Doxygen takes some additional optional keyword arguments:

  • doxyheader: Path of an alternative HTML header
  • doxyfooter: Path of an alternative HTML footer
  • doxycss: Path on an alternative CSS file
  • mydoxyfile: If set to True, don't generate or clean the Doxyfile\
  • senfdoc_path: List of additional directories to search for SENF documentation

8. 'scons' Command line arguments

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 '+='.