9.4. Printing Detailed Build Status: the GetBuildFailures Function

SCons, like most build tools, returns zero status to the shell on success and nonzero status on failure. Sometimes it's useful to give more information about the build status at the end of the run, for instance to print an informative message, send an email, or page the poor slob who broke the build.

SCons provides a GetBuildFailures method that you can use in a python atexit function to get a list of objects describing the actions that failed while attempting to build targets. There can be more than one if you're using -j. Here's a simple example:

import atexit

def print_build_failures():
    from SCons.Script import GetBuildFailures
    for bf in GetBuildFailures():
        print "%s failed: %s" % (bf.node, bf.errstr)
atexit.register(print_build_failures)
      

The atexit.register call registers print_build_failures as an atexit callback, to be called before SCons exits. When that function is called, it calls GetBuildFailures to fetch the list of failed objects. See the man page for the detailed contents of the returned objects; some of the more useful attributes are .node, .errstr, .filename, and .command. The filename is not necessarily the same file as the node; the node is the target that was being built when the error occurred, while the filenameis the file or dir that actually caused the error. Note: only call GetBuildFailures at the end of the build; calling it at any other time is undefined.

Here is a more complete example showing how to turn each element of GetBuildFailures into a string:

# Make the build fail if we pass fail=1 on the command line
if ARGUMENTS.get('fail', 0):
    Command('target', 'source', ['/bin/false'])

def bf_to_str(bf):
    """Convert an element of GetBuildFailures() to a string
    in a useful way."""
    import SCons.Errors
    if bf is None: # unknown targets product None in list
        return '(unknown tgt)'
    elif isinstance(bf, SCons.Errors.StopError):
        return str(bf)
    elif bf.node:
        return str(bf.node) + ': ' + bf.errstr
    elif bf.filename:
        return bf.filename + ': ' + bf.errstr
    return 'unknown failure: ' + bf.errstr
import atexit

def build_status():
    """Convert the build status to a 2-tuple, (status, msg)."""
    from SCons.Script import GetBuildFailures
    bf = GetBuildFailures()
    if bf:
        # bf is normally a list of build failures; if an element is None,
        # it's because of a target that scons doesn't know anything about.
        status = 'failed'
        failures_message = "\n".join(["Failed building %s" % bf_to_str(x)
                           for x in bf if x is not None])
    else:
        # if bf is None, the build completed successfully.
        status = 'ok'
        failures_message = ''
    return (status, failures_message)

def display_build_status():
    """Display the build status.  Called by atexit.
    Here you could do all kinds of complicated things."""
    status, failures_message = build_status()
    if status == 'failed':
       print "FAILED!!!!"  # could display alert, ring bell, etc.
    elif status == 'ok':
       print "Build succeeded."
    print failures_message

atexit.register(display_build_status)
      

When this runs, you'll see the appropriate output:

% scons -Q
scons: `.' is up to date.
Build succeeded.
% scons -Q fail=1
scons: *** [target] Source `source' not found, needed by target `target'.
FAILED!!!!
Failed building target: Source `source' not found, needed by target `target'.