Chapter 21. Pseudo-Builders: the AddMethod function

The AddMethod function is used to add a method to an environment. It's 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"

As mentioned, a psuedo-builder also provides more flexibility in parsing arguments than you can get with a Builder. The next example shows a pseudo-builder with a named argument that modifies the filename, and a separate argument for the 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 used in all subsequently created environments.

     def BuildTestProg(env, testfile, resourcefile, testdir="tests"):
         """Build the test program;
         prepends "test_" to src and target,
         and puts target into testdir."""
         srcfile = "test_%s.c" % testfile
         target = "%s/test_%s" % (testdir, testfile)
         if env['PLATFORM'] == 'win32':
             resfile = env.RES(resourcefile)
             p = env.Program(target, [srcfile, resfile])
             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 /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

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.