$include_dir="/home/hyper-archives/boost-commit/include"; include("$include_dir/msg-header.inc") ?>
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: