1# Status: being written afresh by Vladimir Prus 2 3# Copyright 2007 Vladimir Prus 4# Distributed under the Boost Software License, Version 1.0. 5# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) 6 7# This file is supposed to implement error reporting for Boost.Build. 8# Experience with jam version has shown that printing full backtrace 9# on each error is buffling. Further, for errors printed after parsing -- 10# during target building, the stacktrace does not even mention what 11# target is being built. 12 13# This module implements explicit contexts -- where other code can 14# communicate which projects/targets are being built, and error 15# messages will show those contexts. For programming errors, 16# Python assertions are to be used. 17 18import bjam 19import traceback 20import sys 21 22def format(message, prefix=""): 23 parts = str(message).split("\n") 24 return "\n".join(prefix+p for p in parts) 25 26 27class Context: 28 29 def __init__(self, message, nested=None): 30 self.message_ = message 31 self.nested_ = nested 32 33 def report(self, indent=""): 34 print indent + " -", self.message_ 35 if self.nested_: 36 print indent + " declared at:" 37 for n in self.nested_: 38 n.report(indent + " ") 39 40class JamfileContext: 41 42 def __init__(self): 43 raw = bjam.backtrace() 44 self.raw_ = raw 45 46 def report(self, indent=""): 47 for r in self.raw_: 48 print indent + " - %s:%s" % (r[0], r[1]) 49 50class ExceptionWithUserContext(Exception): 51 52 def __init__(self, message, context, 53 original_exception=None, original_tb=None, stack=None): 54 Exception.__init__(self, message) 55 self.context_ = context 56 self.original_exception_ = original_exception 57 self.original_tb_ = original_tb 58 self.stack_ = stack 59 60 def report(self): 61 print "error:", self.args[0] 62 if self.original_exception_: 63 print format(str(self.original_exception_), " ") 64 print 65 print " error context (most recent first):" 66 for c in self.context_[::-1]: 67 c.report() 68 print 69 if "--stacktrace" in bjam.variable("ARGV"): 70 if self.original_tb_: 71 traceback.print_tb(self.original_tb_) 72 elif self.stack_: 73 for l in traceback.format_list(self.stack_): 74 print l, 75 else: 76 print " use the '--stacktrace' option to get Python stacktrace" 77 print 78 79def user_error_checkpoint(callable): 80 def wrapper(self, *args): 81 errors = self.manager().errors() 82 try: 83 return callable(self, *args) 84 except ExceptionWithUserContext, e: 85 raise 86 except Exception, e: 87 errors.handle_stray_exception(e) 88 finally: 89 errors.pop_user_context() 90 91 return wrapper 92 93class Errors: 94 95 def __init__(self): 96 self.contexts_ = [] 97 self._count = 0 98 99 def count(self): 100 return self._count 101 102 def push_user_context(self, message, nested=None): 103 self.contexts_.append(Context(message, nested)) 104 105 def pop_user_context(self): 106 del self.contexts_[-1] 107 108 def push_jamfile_context(self): 109 self.contexts_.append(JamfileContext()) 110 111 def pop_jamfile_context(self): 112 del self.contexts_[-1] 113 114 def capture_user_context(self): 115 return self.contexts_[:] 116 117 def handle_stray_exception(self, e): 118 raise ExceptionWithUserContext("unexpected exception", self.contexts_[:], 119 e, sys.exc_info()[2]) 120 def __call__(self, message): 121 self._count = self._count + 1 122 raise ExceptionWithUserContext(message, self.contexts_[:], 123 stack=traceback.extract_stack()) 124 125 126def nearest_user_location(): 127 """ 128 Returns: 129 tuple: the filename and line number of the nearest user location 130 """ 131 bt = bjam.backtrace() 132 if not bt: 133 return None 134 last = bt[-1] 135 return last[0], last[1] 136