Chapter 19. Extending SCons: Pseudo-Builders and the AddMethod function

The AddMethod function is used to add a method to an environment. It is typically used to add a "pseudo-builder," a function that looks like a Builder but wraps up calls to multiple other Builders or otherwise processes its arguments before calling one or more Builders.

In the following example, we want to install the program into the standard /usr/bin directory hierarchy, but also copy it into a local install/bin directory from which a package might be built:

def install_in_bin_dirs(env, source):
    """Install source in both bin dirs"""
    i1 = env.Install("$BIN", source)
    i2 = env.Install("$LOCALBIN", source)
    return [i1[0], i2[0]]  # Return a list, like a normal builder

env = Environment(BIN='/usr/bin', LOCALBIN='#install/bin')
env.AddMethod(install_in_bin_dirs, "InstallInBinDirs")
env.InstallInBinDirs(Program('hello.c'))  # installs hello in both bin dirs
     

This produces the following:

% scons -Q /
cc -o hello.o -c hello.c
cc -o hello hello.o
Install file: "hello" as "/usr/bin/hello"
Install file: "hello" as "install/bin/hello"

A pseudo-builder is useful because it gives you more flexibility parsing arguments than you can get with a standard Builder. The next example shows a pseudo-builder with a named argument that modifies the filename, and a separate optional argument for a resource file (rather than having the builder figure it out by file extension). This example also demonstrates using the global AddMethod function to add a method to the global Environment class, so it will be available in all subsequently created environments.

def BuildTestProg(env, testfile, resourcefile="", testdir="tests"):
    """Build the test program.

    Prepends "test_" to src and target and puts the target into testdir.
    If the build is running on Windows, also make use of a resource file,
    if supplied.
    """
    srcfile = f"test_{testfile}.c"
    target = f"{testdir}/test_{testfile}"
    if env['PLATFORM'] == 'win32' and resourcefile:
        resfile = env.RES(resourcefile)
        p = env.Program(target, [srcfile, resfile])
    else:
        p = env.Program(target, srcfile)
    return p

AddMethod(Environment, BuildTestProg)

env = Environment()
env.BuildTestProg('stuff', resourcefile='res.rc')
     

This produces the following on Linux:

% scons -Q
cc -o test_stuff.o -c test_stuff.c
cc -o tests/test_stuff test_stuff.o

And the following on Windows:

C:\>scons -Q
rc /nologo /fores.res res.rc
cl /Fotest_stuff.obj /c test_stuff.c /nologo
link /nologo /OUT:tests\test_stuff.exe test_stuff.obj res.res
embedManifestExeCheck(target, source, env)

Using AddMethod is better than just adding an instance method to a construction environment because it gets called as a proper method, and because AddMethod provides for copying the method to any clones of the construction environment instance.