Progress
FunctionAnother aspect of providing good build output is to give the user feedback about what SCons is doing even when nothing is being built at the moment. This can be especially true for large builds when most of the targets are already up-to-date. Because SCons can take a long time making absolutely sure that every target is, in fact, up-to-date with respect to a lot of dependency files, it can be easy for users to mistakenly conclude that SCons is hung or that there is some other problem with the build.
One way to deal with this perception
is to configure SCons to print something to
let the user know what it's "thinking about."
The Progress
function
allows you to specify a string
that will be printed for every file
that SCons is "considering"
while it is traversing the dependency graph
to decide what targets are or are not up-to-date.
Progress('Evaluating $TARGET\n') Program('f1.c') Program('f2.c')
Note that the Progress
function does not
arrange for a newline to be printed automatically
at the end of the string (as does the Python
print statement),
and we must specify the
\n
that we want printed at the end of the configured string.
This configuration, then,
will have SCons
print that it is Evaluating
each file that it encounters
in turn as it traverses the dependency graph:
% scons -Q Evaluating SConstruct Evaluating f1.c Evaluating f1.o cc -o f1.o -c f1.c Evaluating f1 cc -o f1 f1.o Evaluating f2.c Evaluating f2.o cc -o f2.o -c f2.c Evaluating f2 cc -o f2 f2.o Evaluating .
Of course, normally you don't want to add all of these additional lines to your build output, as that can make it difficult for the user to find errors or other important messages. A more useful way to display this progress might be to have the file names printed directly to the user's screen, not to the same standard output stream where build output is printed, and to use a carriage return character (\r) so that each file name gets re-printed on the same line. Such a configuration would look like:
Progress('$TARGET\r', file=open('/dev/tty', 'w'), overwrite=True) Program('f1.c') Program('f2.c')
Note that we also specified the
overwrite=True argument
to the Progress
function,
which causes SCons to
"wipe out" the previous string with space characters
before printing the next Progress
string.
Without the
overwrite=True argument,
a shorter file name would not overwrite
all of the charactes in a longer file name that
precedes it,
making it difficult to tell what the
actual file name is on the output.
Also note that we opened up the
/dev/tty file
for direct access (on POSIX) to
the user's screen.
On Windows, the equivalent would be to open
the con: file name.
Also, it's important to know that although you can use
$TARGET to substitute the name of
the node in the string,
the Progress
function does not
perform general variable substitution
(because there's not necessarily a construction
environment involved in evaluating a node
like a source file, for example).
You can also specify a list of strings
to the Progress
function,
in which case SCons will
display each string in turn.
This can be used to implement a "spinner"
by having SCons cycle through a
sequence of strings:
Progress(['-\r', '\\\r', '|\r', '/\r'], interval=5) Program('f1.c') Program('f2.c')
Note that here we have also used the
interval=
keyword argument to have SCons
only print a new "spinner" string
once every five evaluated nodes.
Using an interval= count,
even with strings that use $TARGET like
our examples above,
can be a good way to lessen the
work that SCons expends printing Progress
strings,
while still giving the user feedback
that indicates SCons is still
working on evaluating the build.
Lastly, you can have direct control
over how to print each evaluated node
by passing a Python function
(or other Python callable)
to the Progress
function.
Your function will be called
for each evaluated node,
allowing you to
implement more sophisticated logic
like adding a counter:
screen = open('/dev/tty', 'w') count = 0 def progress_function(node) count += 1 screen.write('Node %4d: %s\r' % (count, node)) Progress(progress_function)
Of course, if you choose,
you could completely ignore the
node
argument to the function,
and just print a count,
or anything else you wish.
(Note that there's an obvious follow-on question here: how would you find the total number of nodes that will be evaluated so you can tell the user how close the build is to finishing? Unfortunately, in the general case, there isn't a good way to do that, short of having SCons evaluate its dependency graph twice, first to count the total and the second time to actually build the targets. This would be necessary because you can't know in advance which target(s) the user actually requested to be built. The entire build may consist of thousands of Nodes, for example, but maybe the user specifically requested that only a single object file be built.)