Changes between Initial Version and Version 1 of PythonPort/Design/ErrorReporting


Ignore:
Timestamp:
Oct 28, 2007, 3:23:45 PM (18 years ago)
Author:
ghost
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • PythonPort/Design/ErrorReporting

    v1 v1  
     1
     2= Error reporting guidelines for Boost.Build Python port =
     3
     4== Goals ==
     5
     6 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.
     7 2. For every error, likewise, we should be able to print the context of the error as stacktrace.
     8 3. The stacktrace should ideally show also Jamfiles from where Python is invoked.
     9 4. Python exceptions should never escape into Jam, as Jam cannot handle them in any way.
     10
     11== Concepts ==
     12
     13We'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.
     14
     15For accurate error reporting, all errors that are likely to be caused by user action should be reported via special function, that will
     16record the current user context, so that it can be presented to user.
     17
     18It'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.
     19
     20The error handling documented here is
     21
     22== Reporting errors ==
     23
     24Whenever we detect error that is traceable to user's mistake, we report it like this:
     25{{{
     26self.manager().errors()("something bad happened")
     27}}}
     28
     29This 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.
     30
     31When we detect an error that is totally unclear -- and could possibly be an internal error -- we just raise an exception.
     32
     33== Maintaining user context and handling stray exceptions ==
     34
     35Any function that does something on interest to user must be written like this:
     36{{{
     37try:
     38    self.manager().errors().push_user_context("Doing something")
     39    do_something()
     40except ExceptionWithUserContext, e:
     41    # We have exception with associated user context, just pass along
     42    raise
     43except Exception, e:
     44    # We have exception with no user context. Throw new exception
     45    # that will be associated with the current user context, so that
     46    # we have at least something to report.
     47    self.mananger().errors().handle_stray_exception(e)
     48finally:
     49    # In all cases, restore user context.
     50    self.manager().errors().pop_user_context()
     51}}}
     52
     53== Handling Jam context ==
     54
     55Most errors are result of some code in Jamfile, so it's important to show Jamfile lines in error
     56reports, too. To that effect, each function intended to be called from Jamfiles will call
     57{{{Errors.push_jamfile_context}}} and {{{Errors.pop_jamfile_context}}}. The first call will
     58include current jam stacktrace in the list of context, and that stacktrace will be printed
     59on errors. Note that this logic is completely implemented in project loading code, you
     60don't need to call those functions yourself, ever.
     61
     62== Capturing context ==
     63
     64The targets are created when we parse Jamfiles, and built as a separate step. For all errors during
     65building, it's desirable to report from where the target was declared. To that end:
     66  1. When the target is declared, we grab the current user context, calling {{{Errors.capture_user_context}}}.
     67  2. When actually building, the call to {{{Errors.
     68
     69
     70
     71
     721. For each error, we clearly will be able to produce
     73a raw stacktrace, but it's not very useful. Therefore,
     74we need some 'user-context' mode of error reporting, where
     75user is told what high-level actions were performed when
     76the error happened. For example:
     77
     78        error: invalid feature 'non-existent'
     79        - when computing build properties for target 'foo'
     80        - when build project in '.'
     81
     82To implement this we need:
     83
     84  - Maintain those user-contexts
     85  - Arrange for user-context to be printed or recorded when
     86  error happens
     87  - Do something about 'stray' exceptions through in
     88  Python code, like caused by bugs
     89
     902. We should have a module errors with two functions:
     91'push-context' and 'pop-context'. The first takes a
     92string description of what we're doing now. Every function
     93that does something interesting from the user's point of
     94view should be written like:
     95
     96        try:
     97                errors.push_context("Doing something")
     98                # do something
     99        finally:
     100                errors.pop_context()
     101
     102The try/finally is needed so that if stray exception is thrown,
     103the pushed used context does not stay pushed forever.
     104
     1053. Whenever we run into error condition that can be explained in
     106user terms, we'll use errors.error(). That function will
     107create new exception type, record current user context into
     108it, and raise that exception.
     109
     1104. Python exception should never be allowed to escape back to jam.
     111Every function called from bjam should try/catch and print
     112the exception.
     113
     1145. Every function called from bjam will get bjam stack trace
     115and add it to user context. So, if error happens user will
     116see what line from Jamfile caused it.
     117
     1186. It's possible that we either detect error at too low
     119level to provide any explanation, or the error is detected
     120by Python interpreter. In which case, an ordinary exception
     121will be thrown. To still provide some context, we should
     122use try/catch at various strategic places, catch such stray
     123exceptions, and throw new exception, that holds current user
     124context and the original exception. So, if we try to say
     125interate over None somewhere deep, we'll get an error message
     126like this:
     127
     128        internal-error: Itetarion over non-sequence
     129        - when building target 'foo'
     130
     131        Use --backtrace to see Python backtrace
     132
     133Note that this output is not very useful to debug anything.
     134It's primarily meant to avoid completely freaking out users
     135who are not accustomed to Python stacktraces. This way, they'll
     136at least have the time to open Python docs before getting
     137real Python stracktrace.
     138
     1397. When printing real stacktrace, we can be inside Python code
     140called from Jam code, called from Python code etc. Technically,
     141it appears possible to interleave stacktraces, something like:
     142
     143        build_system.py:100
     144        Jamroot:12
     145        project.py:70