Boost logo

Boost-Commit :

From: ghost_at_[hidden]
Date: 2007-10-28 10:02:07


Author: vladimir_prus
Date: 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
New Revision: 40526
URL: http://svn.boost.org/trac/boost/changeset/40526

Log:
Improve error reporting.

Text files modified:
   branches/build/python_port/python/boost/build/build/errors.py | 98 +++++++++++++++++++++++++++++++++++++++
   branches/build/python_port/python/boost/build/build/feature.py | 4
   branches/build/python_port/python/boost/build/build/project.py | 56 +++++++++++++++-------
   branches/build/python_port/python/boost/build/build/property.py | 5 +
   branches/build/python_port/python/boost/build/build/property_set.py | 2
   branches/build/python_port/python/boost/build/build/targets.py | 15 +++--
   branches/build/python_port/python/boost/build/build_system.py | 15 ++++-
   7 files changed, 161 insertions(+), 34 deletions(-)

Modified: branches/build/python_port/python/boost/build/build/errors.py
==============================================================================
--- branches/build/python_port/python/boost/build/build/errors.py (original)
+++ branches/build/python_port/python/boost/build/build/errors.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -15,8 +15,102 @@
 # messages will show those contexts. For programming errors,
 # Python assertions are to be used.
 
+import bjam
+import traceback
+import sys
+
+def format(message, prefix=""):
+ parts = message.split("\n")
+ return "\n".join(prefix+p for p in parts)
+
+
+class Context:
+
+ def __init__(self, message, nested=None):
+ self.message_ = message
+ self.nested_ = nested
+
+ def report(self, indent=""):
+ print indent + " -", self.message_
+ if self.nested_:
+ print indent + " declared at:"
+ for n in self.nested_:
+ n.report(indent + " ")
+
+class JamfileContext:
+
+ def __init__(self):
+ raw = bjam.backtrace()
+ self.raw_ = raw
+
+ def report(self, indent=""):
+ for r in self.raw_:
+ print indent + " - %s:%s" % (r[0], r[1])
+
+class ExceptionWithUserContext(Exception):
+
+ def __init__(self, message, context,
+ original_exception=None, original_tb=None):
+ Exception.__init__(self, message)
+ self.context_ = context
+ self.original_exception_ = original_exception
+ self.original_tb_ = original_tb
+
+ def report(self):
+ print "error:", self.message
+ if self.original_exception_:
+ print format(self.original_exception_.message, " ")
+ print
+ print " error context (most recent first):"
+ for c in self.context_[::-1]:
+ c.report()
+ print
+
+ if "--stacktrace" in bjam.variable("ARGV"):
+ traceback.print_tb(self.original_tb_)
+ #traceback.print_exc()
+ print "Stacktrace requested"
+
+def user_error_checkpoint(callable):
+ def wrapper(self, *args):
+ errors = self.manager().errors()
+ try:
+ return callable(self, *args)
+ except ExceptionWithUserContext, e:
+ raise
+ except Exception, e:
+ errors.handle_stray_exception(e)
+ finally:
+ errors.pop_user_context()
+
+ return wrapper
+
 class Errors:
 
+ def __init__(self):
+ self.contexts_ = []
+
+ def push_user_context(self, message, nested=None):
+ self.contexts_.append(Context(message, nested))
+
+ def pop_user_context(self):
+ del self.contexts_[-1]
+
+ def push_jamfile_context(self):
+ self.contexts_.append(JamfileContext())
+
+ def pop_jamfile_context(self):
+ del self.contexts_[-1]
+
+ def capture_user_context(self):
+ return self.contexts_[:]
+
+ def handle_stray_exception(self, e):
+ raise ExceptionWithUserContext("unexpected exception", self.contexts_[:],
+ e, sys.exc_info()[2])
     def __call__(self, message):
- # FIXME: add 'error' to each line.
- raise Exception(message)
+ raise ExceptionWithUserContext(message, self.contexts_[:])
+
+
+
+

Modified: branches/build/python_port/python/boost/build/build/feature.py
==============================================================================
--- branches/build/python_port/python/boost/build/build/feature.py (original)
+++ branches/build/python_port/python/boost/build/build/feature.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -494,13 +494,13 @@
                 elif not x in properties: # x is the result of expansion
                     if not f in explicit_features: # not explicitly-specified
                         if f in get_grist (result):
- raise FeatureConflict ("error expansions of composite features result in "
+ raise FeatureConflict ("expansions of composite features result in "
                             "conflicting values for '%s'\nvalues: '%s'\none contributing composite property was '%s'" % (f,
                             get_values (f, result) + [replace_grist (x, '')], p))
                         else:
                             result.append (x)
                 elif f in get_grist (result):
- raise FeatureConflict ("error explicitly-specified values of non-free feature '%s' conflict\n"
+ raise FeatureConflict ("explicitly-specified values of non-free feature '%s' conflict\n"
                     "existing values: '%s'\nvalue from expanding '%s': '%s'" % (f,
                     get_values (f, properties), p, replace_grist (x, '')))
                 else:

Modified: branches/build/python_port/python/boost/build/build/project.py
==============================================================================
--- branches/build/python_port/python/boost/build/build/project.py (original)
+++ branches/build/python_port/python/boost/build/build/project.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -40,6 +40,7 @@
 
 import boost.build.util.path
 from boost.build.build import property_set, property
+from boost.build.build.errors import ExceptionWithUserContext
 import boost.build.build.targets
 
 import bjam
@@ -49,6 +50,7 @@
 import os
 import string
 import imp
+import traceback
 
 class ProjectRegistry:
 
@@ -680,21 +682,9 @@
             self.__dict__[attribute] = specification
             
         elif attribute == "requirements":
- try:
- result = property_set.refine_from_user_input(
- self.requirements, specification,
- self.project_module, self.location)
- except Exception, e:
- # FIXME: any exception caused above is stripped of
- # backtrace.
- print "Conflicting parent properties requirements", e.message
- print dir(e)
- # FIXME:
- #errors.error
- # "Requirements for project at '$(self.location)'"
- # "conflict with parent's." :
- # "Explanation: " $(result[2-]) ;
- self.requirements = result
+ self.requirements = property_set.refine_from_user_input(
+ self.requirements, specification,
+ self.project_module, self.location)
             
         elif attribute == "usage-requirements":
             unconditional = []
@@ -706,7 +696,7 @@
                     unconditional.append(p)
 
             non_free = property.remove("free", unconditional)
- if non_free:
+ if non_free:
                 pass
                 # FIXME:
                 #errors.error "usage-requirements" $(specification) "have non-free properties" $(non-free) ;
@@ -771,9 +761,11 @@
 
     def __init__(self, registry):
         self.registry = registry
+ self.manager_ = registry.manager
         self.rules = {}
         self.local_names = [x for x in self.__class__.__dict__
- if x not in ["__init__", "init_project", "add_rule"]]
+ if x not in ["__init__", "init_project", "add_rule",
+ "error_reporting_wrapper"]]
         self.all_names_ = [x for x in self.local_names]
     
     def add_rule(self, name, callable):
@@ -783,6 +775,29 @@
     def all_names(self):
         return self.all_names_
 
+ def call_and_report_errors(self, callable, *args):
+ result = None
+ try:
+ self.manager_.errors().push_jamfile_context()
+ result = callable(*args)
+ except ExceptionWithUserContext, e:
+ e.report()
+ except Exception, e:
+ print "internal error:", e
+ traceback.print_exc()
+ finally:
+ self.manager_.errors().pop_jamfile_context()
+
+ return result
+
+ def make_wrapper(self, callable):
+ """Given a free-standing function 'callable', return a new
+ callable that will call 'callable' and report all exceptins,
+ using 'call_and_report_errors'."""
+ def wrapper(*args):
+ self.call_and_report_errors(callable, *args)
+ return wrapper
+
     def init_project(self, project_module):
 
         for n in self.local_names:
@@ -794,10 +809,13 @@
                     n = "import"
                 else:
                     n = string.replace(n, "_", "-")
- bjam.import_rule(project_module, n, v)
+
+ bjam.import_rule(project_module, n,
+ self.make_wrapper(v))
 
         for n in self.rules:
- bjam.import_rule(project_module, n, self.rules[n])
+ bjam.import_rule(project_module, n,
+ self.make_wrapper(self.rules[n]))
 
     def project(self, *args):
 

Modified: branches/build/python_port/python/boost/build/build/property.py
==============================================================================
--- branches/build/python_port/python/boost/build/build/property.py (original)
+++ branches/build/python_port/python/boost/build/build/property.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -409,7 +409,10 @@
         feature.validate_value_string (f, property)
 
     if msg:
- raise InvalidFeature ("Invalid property '%s': %s" % (property, msg))
+ # FIXME: don't use globals like this. Import here to
+ # break circular dependency.
+ from boost.build.manager import get_manager
+ get_manager().errors()("Invalid property '%s': %s" % (property, msg))
 
 
 ###################################################################

Modified: branches/build/python_port/python/boost/build/build/property_set.py
==============================================================================
--- branches/build/python_port/python/boost/build/build/property_set.py (original)
+++ branches/build/python_port/python/boost/build/build/property_set.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -56,6 +56,8 @@
     """Creates a property-set from the input given by the user, in the
     context of 'jamfile-module' at 'location'"""
 
+ property.validate(raw_properties)
+
     specification = property.translate_paths(raw_properties, location)
     specification = property.translate_indirect(specification, jamfile_module)
     specification = property.expand_subfeatures_in_conditions(specification)

Modified: branches/build/python_port/python/boost/build/build/targets.py
==============================================================================
--- branches/build/python_port/python/boost/build/build/targets.py (original)
+++ branches/build/python_port/python/boost/build/build/targets.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -81,6 +81,7 @@
 from boost.build.exceptions import *
 from boost.build.util.sequence import unique
 from boost.build.util import set, path
+from boost.build.build.errors import user_error_checkpoint
 
 _re_separate_target_from_properties = re.compile (r'^([^<]*)(/(<.*))?$')
 
@@ -142,7 +143,7 @@
 
         # FIXME: revive after toolset.requirements are ported.
         #specification.append(toolset.requirements)
-
+
         requirements = property_set.refine_from_user_input(
             project.get("requirements"), specification,
             project.project_module, project.get("location"))
@@ -272,11 +273,7 @@
             self.manager_ = project.manager ()
 
         self.name_ = name
- self.project_ = project
-
- # FIXME: do we need this? If yes, how?
- # self.location_ = [ errors.nearest-user-location ] ;
- self.location_ = None
+ self.project_ = project
     
     def manager (self):
         return self.manager_
@@ -822,6 +819,8 @@
         
         # A cache for build requests
         self.request_cache = {}
+
+ self.user_context_ = self.manager_.errors().capture_user_context()
         
     def sources (self):
         """ Returns the list of AbstractTargets which are used as sources.
@@ -1040,11 +1039,15 @@
 
         return (result_var, usage_requirements)
 
+ @user_error_checkpoint
     def generate (self, ps):
         """ Determines final build properties, generates sources,
         and calls 'construct'. This method should not be
         overridden.
         """
+ self.manager_.errors().push_user_context(
+ "Generating target " + self.full_name(), self.user_context_)
+
         if self.manager().targets().logging():
             self.manager().targets().log(
                 "Building target '%s'" % self.name_)

Modified: branches/build/python_port/python/boost/build/build_system.py
==============================================================================
--- branches/build/python_port/python/boost/build/build_system.py (original)
+++ branches/build/python_port/python/boost/build/build_system.py 2007-10-28 10:02:06 EDT (Sun, 28 Oct 2007)
@@ -16,6 +16,7 @@
 import boost.build.tools.common
 import boost.build.tools.builtin
 import boost.build.build.build_request
+from boost.build.build.errors import ExceptionWithUserContext
 
 import bjam
 
@@ -84,6 +85,7 @@
     global argv
     argv = bjam.variable("ARGV")
 
+ # FIXME: document this option.
     if "--profiling" in argv:
         import cProfile
         import pstats
@@ -341,10 +343,15 @@
         manager.set_command_line_free_features(property_set.create(p.free()))
         
         for t in targets:
- g = t.generate(p)
- if not isinstance(t, ProjectTarget):
- results_of_main_targets.extend(g.targets())
- virtual_targets.extend(g.targets())
+ try:
+ g = t.generate(p)
+ if not isinstance(t, ProjectTarget):
+ results_of_main_targets.extend(g.targets())
+ virtual_targets.extend(g.targets())
+ except ExceptionWithUserContext, e:
+ e.report()
+ except Exception:
+ raise
 
     # The cleaning is tricky. Say, if
     # user says:


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk