Chapter 18. Not Writing a Builder: the Command Builder

Creating a Builder and attaching it to a construction environment allows for a lot of flexibility when you want to re-use actions to build multiple files of the same type. This can, however, be cumbersome if you only need to execute one specific command to build a single file (or group of files). For these situations, SCons supports a Command builder that arranges for a specific action to be executed to build a specific file or files. This looks a lot like the other builders (like Program, Object, etc.), but takes as an additional argument the command to be executed to build the file:

env = Environment()
env.Command('foo.out', 'foo.in', "sed 's/x/y/' < $SOURCE > $TARGET")
     

When executed, SCons runs the specified command, substituting $SOURCE and $TARGET as expected:

% scons -Q
sed 's/x/y/' < foo.in > foo.out

This is often more convenient than creating a Builder object and adding it to the $BUILDERS variable of a construction environment.

Note that the action you specify to the Command Builder can be any legal SCons Action, such as a Python function:

env = Environment()

def build(target, source, env):
    # Whatever it takes to build
    return None

env.Command('foo.out', 'foo.in', build)
     

Which executes as follows:

% scons -Q
build(["foo.out"], ["foo.in"])

$SOURCE and $TARGET are expanded in the source and target as well:

env.Command('${SOURCE.base}.out', File('foo.in'), build)
  

Which does the same thing as the previous example, but allows you to write a more generic rule for transforming the source filename to the target filename, since unlike regular Builders, Command does not have any built-in rules for that.

Sidebar: Node Special Attributes

The example uses a Node special attribute (.base, the file without its suffix), a concept which has not been introduced yet, but will appear in several subsequent examples (see details in the Reference Manual section Substitution: Special Attributes). Due to the quirks of SCons' deferred evaluation scheme, node special attribues do not currently work in source and target arguments if the replacement is a string (like 'foo.in'). They do work fine in strings describing actions. You can give SCons a little help by manually converting the filename string to a Node (see Section 5.2, “Explicitly Creating File and Directory Nodes”), which is the approach used in the example.

The method described in Section 9.2, “Controlling How SCons Prints Build Commands: the $*COMSTR Variables” for controlling build output works well when used with pre-defined builders which have pre-defined *COMSTR variables for that purpose, but that is not the case when calling Command, where SCons has no specific knowledge of the action ahead of time. If the action argument to Command is not already an Action object, it will construct one for you with suitable defaults, which include a message based on the type of action. However, you can also construct the Action object yourself to pass to Command, which gives you much more control. Using the action keyword can also help make such lines easier to read. Here's an evolution of the example from above showing this approach:

env = Environment()

def build(target, source, env):
    # Whatever it takes to build
    return None

act = Action(build, cmdstr="Building ${TARGET}")
env.Command('${SOURCE.base}.out', File('foo.in'), action=act)
     

Which executes as follows:

% scons -Q
Building foo.out