wiki:PythonPort/Design/ErrorReporting

Error reporting guidelines for Boost.Build Python port

Goals

  1. For every error, we should be able to print the context of the error in user-visible terms, like "error when building target XXX". It should be necessary to associate error messages with Jamfiles, as much as possible -- for example, any error about building a target should say where that target is declared.
  2. For every error, likewise, we should be able to print the context of the error as stacktrace.
  3. The stacktrace should ideally show also Jamfiles from where Python is invoked.
  4. Python exceptions should never escape into Jam, as Jam cannot handle them in any way.

Concepts

We'll introduce 'user context', which is string description of what is being done now. The code will be pushing and popping user context as it does things. At any time, we'll have a list of currently active, nested, user context.

For accurate error reporting, all errors that are likely to be caused by user action should be reported via special function, that will record the current user context, so that it can be presented to user.

It's possible that we detect an error that is not directly traceable to user action, or Python interpreter itself detects an error and raise an Exception. We'll have try/except blocks at strategic places to associate such exceptions with user contexts.

The error handling documented here is implemented by the boost.build.build.errors module. The main class there is Errors, and an instance of that call can be obtained by calling the errors() method on the Manager class.

Reporting errors

Whenever we detect error that is traceable to user's mistake, we report it like this:

self.manager().errors()("something bad happened")

This call with create a raise an instance of ExceptionWithUserContext class, which keeps a record of what user-visible actions were performed at the point of error detection. This class also can print intself in a nice way.

When we detect an error that is totally unclear -- and could possibly be an internal error -- we just raise an exception.

Maintaining user context and handling stray exceptions

Any function that does something on interest to user must be written like this:

try:
    self.manager().errors().push_user_context("Doing something")
    do_something()
except ExceptionWithUserContext, e:
    # We have exception with associated user context, just pass along
    raise
except Exception, e:
    # We have exception with no user context. Throw new exception
    # that will be associated with the current user context, so that
    # we have at least something to report.
    self.mananger().errors().handle_stray_exception(e)
finally:
    # In all cases, restore user context. 
    self.manager().errors().pop_user_context()

Since writing this all over is not fun, the errors module provides a function decorator user_error_checkpoint, that allows to write your code like this:

@user_error_checkpoint
def do_something():
    self.manager().errors()("doing something")
    ....

Both approaches above assume that a function only pushes a user context once, if it pushes two nested context the first approach must be extended to use two try/except blocks, or the function split in two.

Handling Jam context

Most errors are result of some code in Jamfile, so it's important to show Jamfile lines in error reports, too. To that effect, each function intended to be called from Jamfiles will call Errors.push_jamfile_context and Errors.pop_jamfile_context. The first call will include current jam stacktrace in the list of context, and that stacktrace will be printed on errors. Note that this logic is completely implemented in project loading code, you don't need to call those functions yourself, ever.

Capturing context

The targets are created when we parse Jamfiles, and built as a separate step. For all errors during building, it's desirable to report from where the target was declared. To that end:

  1. When the target is declared, we grab the current user context, calling Errors.capture_user_context.
  2. When actually building, the call to Errors.push_user_context should provide a message describing what is done, and additionally, pass the previously captured context as the value of the nested parameter.

Top-level error reporting

All functions called from Jam, including the main function of Boost.Build, will have try/catch blocks. When ExceptionWithUserContext is caught, its report method will be printed. Should stray exception be caught, despite all efforts, we'll also nicely print it.

Raw backtrace

[The behaviour described in this section is not yet implemented]

If a --stacktrace parameter is specified on the command line, we'll print raw Python backtrace. Such a backtrace can include Python code called from Jam code, called from Python code etc. Technically, it appears possible to interleave stacktraces, something like:

    build_system.py:100
    Jamroot:12
    project.py:70

an so we'll try to do that.

Last modified 17 years ago Last modified on Oct 28, 2007, 4:24:38 PM
Note: See TracWiki for help on using the wiki.