diff options
Diffstat (limited to 'doc/user/output.xml')
-rw-r--r-- | doc/user/output.xml | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/doc/user/output.xml b/doc/user/output.xml new file mode 100644 index 0000000..d42457a --- /dev/null +++ b/doc/user/output.xml @@ -0,0 +1,691 @@ +<!-- + + __COPYRIGHT__ + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--> + + <para> + + A key aspect of creating a usable build configuration + is providing good output from the build + so its users can readily understand + what the build is doing + and get information about how to control the build. + &SCons; provides several ways of + controlling output from the build configuration + to help make the build + more useful and understandable. + + </para> + + <section> + <title>Providing Build Help: the &Help; Function</title> + + <para> + + It's often very useful to be able to give + users some help that describes the + specific targets, build options, etc., + that can be used for your build. + &SCons; provides the &Help; function + to allow you to specify this help text: + + </para> + + <programlisting> + Help(""" + Type: 'scons program' to build the production program, + 'scons debug' to build the debug version. + """) + </programlisting> + + <para> + + (Note the above use of the Python triple-quote syntax, + which comes in very handy for + specifying multi-line strings like help text.) + + </para> + + <para> + + When the &SConstruct; or &SConscript; files + contain such a call to the &Help; function, + the specified help text will be displayed in response to + the &SCons; <literal>-h</literal> option: + + </para> + + <screen> + % <userinput>scons -h</userinput> + scons: Reading SConscript files ... + scons: done reading SConscript files. + + Type: 'scons program' to build the production program, + 'scons debug' to build the debug version. + + Use scons -H for help about command-line options. + </screen> + + <para> + + The &SConscript; files may contain + multiple calls to the &Help; function, + in which case the specified text(s) + will be concatenated when displayed. + This allows you to split up the + help text across multiple &SConscript; files. + In this situation, the order in + which the &SConscript; files are called + will determine the order in which the &Help; functions are called, + which will determine the order in which + the various bits of text will get concatenated. + + </para> + + <para> + + Another use would be to make the help text conditional + on some variable. + For example, suppose you only want to display + a line about building a Windows-only + version of a program when actually + run on Windows. + The following &SConstruct; file: + + </para> + + <programlisting> + env = Environment() + + Help("\nType: 'scons program' to build the production program.\n") + + if env['PLATFORM'] == 'win32': + Help("\nType: 'scons windebug' to build the Windows debug version.\n") + </programlisting> + + <para> + + Will display the complete help text on Windows: + + </para> + + <screen> + C:\><userinput>scons -h</userinput> + scons: Reading SConscript files ... + scons: done reading SConscript files. + + Type: 'scons program' to build the production program. + + Type: 'scons windebug' to build the Windows debug version. + + Use scons -H for help about command-line options. + </screen> + + <para> + + But only show the relevant option on a Linux or UNIX system: + + </para> + + <screen> + % <userinput>scons -h</userinput> + scons: Reading SConscript files ... + scons: done reading SConscript files. + + Type: 'scons program' to build the production program. + + Use scons -H for help about command-line options. + </screen> + + <para> + + If there is no &Help; text in the &SConstruct; or + &SConscript; files, + &SCons; will revert to displaying its + standard list that describes the &SCons; command-line + options. + This list is also always displayed whenever + the <literal>-H</literal> option is used. + + </para> + + </section> + + <section> + <title>Controlling How &SCons; Prints Build Commands: the <envar>$*COMSTR</envar> Variables</title> + + <para> + + Sometimes the commands executed + to compile object files or link programs + (or build other targets) + can get very long, + long enough to make it difficult for users + to distinguish error messages or + other important build output + from the commands themselves. + All of the default <envar>$*COM</envar> variables + that specify the command lines + used to build various types of target files + have a corresponding <envar>$*COMSTR</envar> variable + that can be set to an alternative + string that will be displayed + when the target is built. + + </para> + + <para> + + For example, suppose you want to + have &SCons; display a + <literal>"Compiling"</literal> + message whenever it's compiling an object file, + and a + <literal>"Linking"</literal> + when it's linking an executable. + You could write a &SConstruct; file + that looks like: + + </para> + + <programlisting> + env = Environment(CCCOMSTR = "Compiling $TARGET", + LINKCOMSTR = "Linking $TARGET") + env.Program('foo.c') + </programlisting> + + <para> + + Which would then yield the output: + + </para> + + <!-- + + <scons_output example="COMSTR" os="posix"> + <scons_output_command>scons -Q</scons_output_command> + </scons_output> + + --> + + <screen> + % <userinput>scons -Q</userinput> + Compiling foo.o + Linking foo + </screen> + + <para> + + &SCons; performs complete variable substitution + on <envar>$*COMSTR</envar> variables, + so they have access to all of the + standard variables like &cv-TARGET; &cv-SOURCES;, etc., + as well as any construction variables + that happen to be configured in + the construction environment + used to build a specific target. + + </para> + + <para> + + Of course, sometimes it's still important to + be able to see the exact command + that &SCons; will execute to build a target. + For example, you may simply need to verify + that &SCons; is configured to supply + the right options to the compiler, + or a developer may want to + cut-and-paste a comiloe command + to add a few options + for a custom test. + + </para> + + <para> + + One common way to give users + control over whether or not + &SCons; should print the actual command line + or a short, configured summary + is to add support for a + <varname>VERBOSE</varname> + command-line variable to your &SConstruct; file. + A simple configuration for this might look like: + + </para> + + <programlisting> + env = Environment() + if ARGUMENTS.get('VERBOSE') != "1': + env['CCCOMSTR'] = "Compiling $TARGET" + env['LINKCOMSTR'] = "Linking $TARGET" + env.Program('foo.c') + </programlisting> + + <para> + + + By only setting the appropriate + <envar>$*COMSTR</envar> variables + if the user specifies + <literal>VERBOSE=1</literal> + on the command line, + the user has control + over how &SCons; + displays these particular command lines: + + </para> + + <!-- + + <scons_output example="COMSTR-VERBOSE" os="posix"> + <scons_output_command>scons -Q</scons_output_command> + <scons_output_command>scons -Q -c</scons_output_command> + <scons_output_command>scons -Q VERBOSE=1</scons_output_command> + </scons_output> + + --> + + <screen> + % <userinput>scons -Q</userinput> + Compiling foo.o + Linking foo + % <userinput>scons -Q -c</userinput> + Removed foo.o + Removed foo + % <userinput>scons -Q VERBOSE=1</userinput> + cc -o foo.o -c foo.c + cc -o foo foo.o + </screen> + + </section> + + <section> + <title>Providing Build Progress Output: the &Progress; Function</title> + + <para> + + Another 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. + + </para> + + <para> + + 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. + + </para> + + <programlisting> + Progress('Evaluating $TARGET\n') + Program('f1.c') + Program('f2.c') + </programlisting> + + <para> + + 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 + <literal>print</literal> statement), + and we must specify the + <literal>\n</literal> + that we want printed at the end of the configured string. + This configuration, then, + will have &SCons; + print that it is <literal>Evaluating</literal> + each file that it encounters + in turn as it traverses the dependency graph: + + </para> + + <screen> + % <userinput>scons -Q</userinput> + 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 . + </screen> + + <para> + + 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 + (<literal>\r</literal>) + so that each file name gets re-printed on the same line. + Such a configuration would look like: + + </para> + + <programlisting> + Progress('$TARGET\r', + file=open('/dev/tty', 'w'), + overwrite=True) + Program('f1.c') + Program('f2.c') + </programlisting> + + <para> + + Note that we also specified the + <literal>overwrite=True</literal> 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 + <literal>overwrite=True</literal> 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 + <filename>/dev/tty</filename> file + for direct access (on POSIX) to + the user's screen. + On Windows, the equivalent would be to open + the <filename>con:</filename> file name. + + </para> + + <para> + + Also, it's important to know that although you can use + <literal>$TARGET</literal> to substitute the name of + the node in the string, + the &Progress; function does <emphasis>not</emphasis> + perform general variable substitution + (because there's not necessarily a construction + environment involved in evaluating a node + like a source file, for example). + + </para> + + <para> + + 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: + + </para> + + <programlisting> + Progress(['-\r', '\\\r', '|\r', '/\r'], interval=5) + Program('f1.c') + Program('f2.c') + </programlisting> + + <para> + + Note that here we have also used the + <literal>interval=</literal> + keyword argument to have &SCons; + only print a new "spinner" string + once every five evaluated nodes. + Using an <literal>interval=</literal> count, + even with strings that use <literal>$TARGET</literal> 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. + + </para> + + <para> + + 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: + + </para> + + <programlisting> + screen = open('/dev/tty', 'w') + count = 0 + def progress_function(node) + count += 1 + screen.write('Node %4d: %s\r' % (count, node)) + + Progress(progress_function) + </programlisting> + + <para> + + Of course, if you choose, + you could completely ignore the + <varname>node</varname> argument to the function, + and just print a count, + or anything else you wish. + + </para> + + <para> + + (Note that there's an obvious follow-on question here: + how would you find the total number of nodes + that <emphasis>will be</emphasis> + 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.) + + </para> + + </section> + + <section> + <title>Printing Detailed Build Status: the &GetBuildFailures; Function</title> + + <para> + + 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. + + </para> + + <para> + + SCons provides a &GetBuildFailures; method that + you can use in a python <function>atexit</function> 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 <literal>-j</literal>. Here's a + simple example: + + </para> + + <programlisting> + 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) + </programlisting> + + <para> + + The <function>atexit.register</function> call + registers <function>print_build_failures</function> + as an <function>atexit</function> 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 + <literal>.node</literal>, + <literal>.errstr</literal>, + <literal>.filename</literal>, and + <literal>.command</literal>. + The <literal>filename</literal> is not necessarily + the same file as the <literal>node</literal>; the + <literal>node</literal> is the target that was + being built when the error occurred, while the + <literal>filename</literal>is 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. + + </para> + + <para> + + Here is a more complete example showing how to + turn each element of &GetBuildFailures; into a string: + + </para> + + <programlisting> + # 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) + </programlisting> + + <para> + + When this runs, you'll see the appropriate output: + + </para> + + <screen> + % <userinput>scons -Q</userinput> + scons: `.' is up to date. + Build succeeded. + % <userinput>scons -Q fail=1</userinput> + scons: *** Source `source' not found, needed by target `target'. Stop. + FAILED!!!! + Failed building Source `source' not found, needed by target `target'. + </screen> + + </section> |