Applied Benoit Belley's patch in ticket 1957 improve the robustness of
authorgaryo <garyo@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 29 Oct 2008 03:01:00 +0000 (03:01 +0000)
committergaryo <garyo@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 29 Oct 2008 03:01:00 +0000 (03:01 +0000)
GetBuildFailures().  New function convert_to_buildError, and use it in
several places so all build failures now go through it and are
returned as BuildError exceptions.  Had a small effect on output
formatting in many tests but no significant change to behavior.  I
reworked the patch a little to keep SCons exit status values the same
as before; this patch could make it simpler to change them in some
cases, e.g. exit with the errno of the failed action if desired.  One
nice side effect of this patch is that more scons errors print the
node that caused the error now.

git-svn-id: http://scons.tigris.org/svn/scons/trunk@3744 fdb21ef1-2011-0410-befe-b5e4ea1792b1

25 files changed:
QMTest/TestCommon.py
src/engine/SCons/Action.py
src/engine/SCons/CacheDirTests.py
src/engine/SCons/Errors.py
src/engine/SCons/ErrorsTests.py
src/engine/SCons/Executor.py
src/engine/SCons/Node/FSTests.py
src/engine/SCons/Script/Main.py
src/engine/SCons/Taskmaster.py
src/engine/SCons/TaskmasterTests.py
src/script/scons.py
test/Alias/errors.py
test/Configure/Action-error.py
test/Errors/Exception.py
test/GetBuildFailures/serial.py
test/Libs/SharedLibrary.py
test/Parallel/multiple-parents.py
test/RCS/diskcheck.py
test/Scanner/exception.py
test/builderrors.py
test/exceptions.py
test/nonexistent.py
test/option/debug-stacktrace.py
test/symlink/dangling-include.py
test/symlink/dangling-source.py

index 8a3ad370864ac82c23d6393136da979b2f94ffc0..167f84da31c6bd121e555f63c66477ffb44723cc 100644 (file)
@@ -220,12 +220,13 @@ if os.name == 'posix':
             return None
         return _status(self) != status
     def _status(self):
-        if os.WIFEXITED(self.status):
-            return os.WEXITSTATUS(self.status)
-        elif os.WIFSIGNALED(self.status):
-            return os.WTERMSIG(self.status)
-        else:
-            return None
+        return self.status      # p.wait() has already retrieved the OS status from the status val; don't do it again here!
+#         if os.WIFEXITED(self.status):
+#             return os.WEXITSTATUS(self.status)
+#         elif os.WIFSIGNALED(self.status):
+#             return os.WTERMSIG(self.status)
+#         else:
+#             return None
 elif os.name == 'nt':
     def _failed(self, status = 0):
         return not (self.status is None or status is None) and \
index c24d0be8aabb18210e6b23acb3bd4cd8ea57b935..cfd4a50c57cac72804478aef1aab705a5f27a0ac 100644 (file)
@@ -946,31 +946,38 @@ class FunctionAction(_ActionAction):
         return "%s(target, source, env)" % name
 
     def execute(self, target, source, env):
-        rsources = map(rfile, source)
+        exc_info = (None,None,None)
         try:
-            result = self.execfunction(target=target, source=rsources, env=env)
-        except EnvironmentError, e:
-            # If an IOError/OSError happens, raise a BuildError.
-            # Report the name of the file or directory that caused the
-            # error, which might be different from the target being built
-            # (for example, failure to create the directory in which the
-            # target file will appear).
-            try: filename = e.filename
-            except AttributeError: filename = None
-            result = SCons.Errors.BuildError(node=target,
-                                             errstr=e.strerror,
-                                             status=1,
-                                             filename=filename,
-                                             action=self,
-                                             command=self.strfunction(target, source, env))
-        else:
+            rsources = map(rfile, source)
+            try:
+                result = self.execfunction(target=target, source=rsources, env=env)
+            except Exception, e:
+                result = e
+                exc_info = sys.exc_info()
+
             if result:
-                msg = "Error %s" % result
-                result = SCons.Errors.BuildError(errstr=msg,
-                                                 status=result,
-                                                 action=self,
-                                                 command=self.strfunction(target, source, env))
-        return result
+                result = SCons.Errors.convert_to_BuildError(result, exc_info)
+                result.node=target
+                result.action=self
+                result.command=self.strfunction(target, source, env)
+
+                # FIXME: This maintains backward compatibility with respect to
+                # which type of exceptions were returned by raising an
+                # exception and which ones were returned by value. It would
+                # probably be best to always return them by value here, but
+                # some codes do not check the return value of Actions and I do
+                # not have the time to modify them at this point.
+                if (exc_info[1] and 
+                    not isinstance(exc_info[1],EnvironmentError)):
+                    raise result
+
+            return result
+        finally:
+            # Break the cycle between the traceback object and this
+            # function stack frame. See the sys.exc_info() doc info for
+            # more information about this issue.
+            del exc_info
+
 
     def get_presig(self, target, source, env):
         """Return the signature contents of this callable action."""
index e31f67bcf9def3e9ec5c85384ebbfad0ad137b70..3715bf17d901db871ef4b8b1cd089fc7b463a379 100644 (file)
@@ -241,7 +241,8 @@ class FileTestCase(BaseTestCase):
             warn_caught = 0
             try:
                 f7.built()
-            except SCons.Warnings.CacheWriteErrorWarning:
+            except SCons.Errors.BuildError, e:
+                assert e.exc_info[0] == SCons.Warnings.CacheWriteErrorWarning
                 warn_caught = 1
             assert warn_caught
         finally:
index fc55cf45aaa1128a23b20ce63925579821ccec8e..e663fb48c7197918117387de3afa89c153eb27ec 100644 (file)
@@ -30,20 +30,88 @@ and user errors in SCons.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+import SCons.Util
 
+import exceptions
 
 class BuildError(Exception):
-    def __init__(self, node=None, errstr="Unknown error", status=0,
-                       filename=None, executor=None, action=None, command=None,
-                       *args):
-        self.node = node
+    """ Errors occuring while building.
+
+    BuildError have the following attributes:
+
+        Information about the cause of the build error:
+        -----------------------------------------------
+
+        errstr : a description of the error message
+
+        status : the return code of the action that caused the build
+                 error. Must be set to a non-zero value even if the
+                 build error is not due to an action returning a
+                 non-zero returned code.
+
+        exitstatus : SCons exit status due to this build error.
+                     Must be nonzero unless due to an explicit Exit()
+                     call.  Not always the same as status, since
+                     actions return a status code that should be
+                     respected, but SCons typically exits with 2
+                     irrespective of the return value of the failed
+                     action.
+
+        filename : The name of the file or directory that caused the
+                   build error. Set to None if no files are associated with
+                   this error. This might be different from the target
+                   being built. For example, failure to create the
+                   directory in which the target file will appear. It
+                   can be None if the error is not due to a particular
+                   filename.
+
+        exc_info : Info about exception that caused the build
+                   error. Set to (None, None, None) if this build
+                   error is not due to an exception.
+
+
+        Information about the cause of the location of the error:
+        ---------------------------------------------------------
+
+        node : the error occured while building this target node(s)
+        
+        executor : the executor that caused the build to fail (might
+                   be None if the build failures is not due to the
+                   executor failing)
+        
+        action : the action that caused the build to fail (might be
+                 None if the build failures is not due to the an
+                 action failure)
+
+        command : the command line for the action that caused the
+                  build to fail (might be None if the build failures
+                  is not due to the an action failure)
+        """
+
+    def __init__(self, 
+                 node=None, errstr="Unknown error", status=2, exitstatus=2,
+                 filename=None, executor=None, action=None, command=None,
+                 exc_info=(None, None, None)):
+        
         self.errstr = errstr
         self.status = status
+        self.exitstatus = exitstatus
         self.filename = filename
+        self.exc_info = exc_info
+
+        self.node = node
         self.executor = executor
         self.action = action
         self.command = command
-        apply(Exception.__init__, (self,) + args)
+
+        Exception.__init__(self, node, errstr, status, exitstatus, filename, 
+                           executor, action, command, exc_info)
+
+    def __str__(self):
+        if self.filename:
+            return self.filename + ': ' + self.errstr
+        else:
+            return self.errstr
 
 class InternalError(Exception):
     pass
@@ -61,11 +129,68 @@ class ExplicitExit(Exception):
     def __init__(self, node=None, status=None, *args):
         self.node = node
         self.status = status
+        self.exitstatus = status
         apply(Exception.__init__, (self,) + args)
 
-class TaskmasterException(Exception):
-    def __init__(self, node=None, exc_info=(None, None, None), *args):
-        self.node = node
-        self.errstr = "Exception"
-        self.exc_info = exc_info
-        apply(Exception.__init__, (self,) + args)
+def convert_to_BuildError(status, exc_info=None):
+    """
+    Convert any return code a BuildError Exception.
+
+    `status' can either be a return code or an Exception.
+    The buildError.status we set here will normally be
+    used as the exit status of the "scons" process.
+    """
+    if not exc_info and isinstance(status, Exception):
+        exc_info = (status.__class__, status, None)
+
+    if isinstance(status, BuildError):
+        buildError = status
+        buildError.exitstatus = 2   # always exit with 2 on build errors
+    elif isinstance(status, ExplicitExit):
+        status = status.status
+        errstr = 'Explicit exit, status %s' % status
+        buildError = BuildError(
+            errstr=errstr,
+            status=status,      # might be 0, OK here
+            exitstatus=status,      # might be 0, OK here
+            exc_info=exc_info)
+    elif isinstance(status, (StopError, UserError)):
+        buildError = BuildError(
+            errstr=str(status),
+            status=2,
+            exitstatus=2,
+            exc_info=exc_info)
+    elif isinstance(status, exceptions.EnvironmentError):
+        # If an IOError/OSError happens, raise a BuildError.
+        # Report the name of the file or directory that caused the
+        # error, which might be different from the target being built
+        # (for example, failure to create the directory in which the
+        # target file will appear).
+        try: filename = status.filename
+        except AttributeError: filename = None
+        buildError = BuildError( 
+            errstr=status.strerror,
+            status=status.errno,
+            exitstatus=2,
+            filename=filename,
+            exc_info=exc_info)
+    elif isinstance(status, Exception):
+        buildError = BuildError(
+            errstr='%s : %s' % (status.__class__.__name__, status),
+            status=2,
+            exitstatus=2,
+            exc_info=exc_info)
+    elif SCons.Util.is_String(status):
+        buildError = BuildError(
+            errstr=status,
+            status=2,
+            exitstatus=2)
+    else:
+        buildError = BuildError(
+            errstr="Error %s" % status,
+            status=status,
+            exitstatus=2)
+    
+    #import sys
+    #sys.stderr.write("convert_to_BuildError: status %s => (errstr %s, status %s)"%(status,buildError.errstr, buildError.status))
+    return buildError
index 93d516c8907d6c63bd9c0916db60f7598958d6a4..893d2842a58753b26330cb00d675caceee28376d 100644 (file)
@@ -32,10 +32,49 @@ class ErrorsTestCase(unittest.TestCase):
     def test_BuildError(self):
         """Test the BuildError exception."""
         try:
-            raise SCons.Errors.BuildError(node = "n", errstr = "foo")
+            raise SCons.Errors.BuildError(
+                errstr = "foo", status=57, filename="file", exc_info=(1,2,3),
+                node = "n", executor="e", action="a", command="c")
         except SCons.Errors.BuildError, e:
-            assert e.node == "n"
             assert e.errstr == "foo"
+            assert e.status == 57
+            assert e.exitstatus == 2, e.exitstatus
+            assert e.filename == "file"
+            assert e.exc_info == (1,2,3)
+
+            assert e.node == "n"
+            assert e.executor == "e"
+            assert e.action == "a"
+            assert e.command == "c"
+
+        try:
+            raise SCons.Errors.BuildError("n", "foo", 57, 3, "file", 
+                                          "e", "a", "c", (1,2,3))
+        except SCons.Errors.BuildError, e:
+            assert e.errstr == "foo", e.errstr
+            assert e.status == 57, e.status
+            assert e.exitstatus == 3, e.exitstatus
+            assert e.filename == "file", e.filename
+            assert e.exc_info == (1,2,3), e.exc_info
+
+            assert e.node == "n"
+            assert e.executor == "e"
+            assert e.action == "a"
+            assert e.command == "c"
+
+        try:
+            raise SCons.Errors.BuildError()
+        except SCons.Errors.BuildError, e:
+            assert e.errstr == "Unknown error"
+            assert e.status == 2
+            assert e.exitstatus == 2
+            assert e.filename == None
+            assert e.exc_info == (None, None, None)
+
+            assert e.node == None
+            assert e.executor == None
+            assert e.action == None
+            assert e.command == None
 
     def test_InternalError(self):
         """Test the InternalError exception."""
@@ -58,14 +97,6 @@ class ErrorsTestCase(unittest.TestCase):
         except SCons.Errors.ExplicitExit, e:
             assert e.node == "node"
 
-    def test_TaskmasterException(self):
-        """Test the TaskmasterException exception."""
-        try:
-            raise SCons.Errors.TaskmasterException("tm exception", (1, 2, 3))
-        except SCons.Errors.TaskmasterException, e:
-            assert e.node == "tm exception"
-            assert e.exc_info == (1, 2, 3)
-
 if __name__ == "__main__":
     suite = unittest.makeSuite(ErrorsTestCase, 'test_')
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
index fe3cb622c30bb7b15bea966c216563c24c90d1eb..6b9ab0f4ba10d72d722c4906bce67ea4f89df4dc 100644 (file)
@@ -134,7 +134,11 @@ class Executor:
                 raise status
             elif status:
                 msg = "Error %s" % status
-                raise SCons.Errors.BuildError(errstr=msg, executor=self, action=act)
+                raise SCons.Errors.BuildError(
+                    errstr=msg, 
+                    node=self.targets,
+                    executor=self, 
+                    action=act)
         return status
 
     # use extra indirection because with new-style objects (Python 2.2
index 4081a7a3c4bab5fa84b53f79f7d6d0590e2b29ef..3d1afdc3ee0cdcd0446dba2939dda662be887174 100644 (file)
@@ -361,7 +361,7 @@ class VariantDirTestCase(unittest.TestCase):
 
         save_Link = SCons.Node.FS.Link
         def Link_IOError(target, source, env):
-            raise IOError, "Link_IOError"
+            raise IOError, (17, "Link_IOError")
         SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None)
 
         test.write(['work', 'src', 'IOError'], "work/src/IOError\n")
index 7b6b52b33b577f4fd51241955d49db5234d21573..62a52d60b0f369c028a9bc39e051d44430cd44ee 100644 (file)
@@ -231,54 +231,51 @@ class BuildTask(SCons.Taskmaster.Task):
         # Handle the failure of a build task.  The primary purpose here
         # is to display the various types of Errors and Exceptions
         # appropriately.
-        status = 2
         exc_info = self.exc_info()
         try:
             t, e, tb = exc_info
         except ValueError:
             t, e = exc_info
             tb = None
+
         if t is None:
             # The Taskmaster didn't record an exception for this Task;
             # see if the sys module has one.
-            t, e = sys.exc_info()[:2]
-
-        def nodestring(n):
-            if not SCons.Util.is_List(n):
-                n = [ n ]
-            return string.join(map(str, n), ', ')
+            try:
+                t, e, tb = sys.exc_info()[:]
+            except ValueError:
+                t, e = exc_info
+                tb = None
+                
+        # Deprecated string exceptions will have their string stored
+        # in the first entry of the tuple.
+        if e is None:
+            e = t
+
+        buildError = SCons.Errors.convert_to_BuildError(e)
+        if not buildError.node:
+            buildError.node = self.node
+
+        node = buildError.node
+        if not SCons.Util.is_List(node):
+                node = [ node ]
+        nodename = string.join(map(str, node), ', ')
 
         errfmt = "scons: *** [%s] %s\n"
+        sys.stderr.write(errfmt % (nodename, buildError))
 
-        if t == SCons.Errors.BuildError:
-            tname = nodestring(e.node)
-            errstr = e.errstr
-            if e.filename:
-                errstr = e.filename + ': ' + errstr
-            sys.stderr.write(errfmt % (tname, errstr))
-        elif t == SCons.Errors.TaskmasterException:
-            tname = nodestring(e.node)
-            sys.stderr.write(errfmt % (tname, e.errstr))
-            type, value, trace = e.exc_info
+        if (buildError.exc_info[2] and buildError.exc_info[1] and 
+            not isinstance(
+                buildError.exc_info[1], 
+                (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))):
+            type, value, trace = buildError.exc_info
             traceback.print_exception(type, value, trace)
-        elif t == SCons.Errors.ExplicitExit:
-            status = e.status
-            tname = nodestring(e.node)
-            errstr = 'Explicit exit, status %s' % status
-            sys.stderr.write(errfmt % (tname, errstr))
-        else:
-            if e is None:
-                e = t
-            s = str(e)
-            if t == SCons.Errors.StopError and not self.options.keep_going:
-                s = s + '  Stop.'
-            sys.stderr.write("scons: *** %s\n" % s)
-
-            if tb and print_stacktrace:
-                sys.stderr.write("scons: internal stack trace:\n")
-                traceback.print_tb(tb, file=sys.stderr)
+        elif tb and print_stacktrace:
+            sys.stderr.write("scons: internal stack trace:\n")
+            traceback.print_tb(tb, file=sys.stderr)
 
-        self.do_failed(status)
+        self.exception = (e, buildError, tb) # type, value, traceback
+        self.do_failed(buildError.exitstatus)
 
         self.exc_clear()
 
@@ -1273,6 +1270,8 @@ def main():
     except SConsPrintHelpException:
         parser.print_help()
         exit_status = 0
+    except SCons.Errors.BuildError, e:
+        exit_status = e.exitstatus
     except:
         # An exception here is likely a builtin Python exception Python
         # code in an SConscript file.  Show them precisely what the
index f8c494d06df609fe26dabeb809640f3f0df7b1e1..979b9e9449bcd62068eb3c7e2c41900ee916147e 100644 (file)
@@ -225,9 +225,11 @@ class Task:
             raise
         except SCons.Errors.BuildError:
             raise
-        except:
-            raise SCons.Errors.TaskmasterException(self.targets[0],
-                                                   sys.exc_info())
+        except Exception, e:
+            buildError = SCons.Errors.convert_to_BuildError(e)
+            buildError.node = self.targets[0]
+            buildError.exc_info = sys.exc_info()
+            raise buildError
 
     def executed_without_callbacks(self):
         """
index 932458179f0550f8859b16b2468b3401cb4c7f74..0735cc1c5bf1a62db58909f5d35c101302f95b88 100644 (file)
@@ -876,9 +876,9 @@ class TaskmasterTestCase(unittest.TestCase):
         t = tm.next_task()
         try:
             t.execute()
-        except SCons.Errors.TaskmasterException, e:
+        except SCons.Errors.BuildError, e:
             assert e.node == n4, e.node
-            assert e.errstr == "Exception", e.errstr
+            assert e.errstr == "OtherError : ", e.errstr
             assert len(e.exc_info) == 3, e.exc_info
             exc_traceback = sys.exc_info()[2]
             assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2]
index d8ad7b24c3248a6f99ba8ff08e45d0ec314616c3..698c9b4393d98aa5c37fa259cbe2c2e6dac4a42d 100644 (file)
@@ -160,4 +160,6 @@ sys.path = libs + sys.path
 
 if __name__ == "__main__":
     import SCons.Script
+    # this does all the work, and calls sys.exit
+    # with the proper exit status when done.
     SCons.Script.main()
index 1205b29736f4ad4994ef495b177118761df126db..36876c4435c26de8b45ec77b782f22981cca7e0b 100644 (file)
@@ -40,7 +40,7 @@ env.Alias('A', 'B')
 """)
 
 test.run(arguments='A',
-         stderr="scons: *** Source `D' not found, needed by target `C'.  Stop.\n",
+         stderr="scons: *** [C] Source `D' not found, needed by target `C'.\n",
          status=2)
 
 test.pass_test()
index 0abcc7ce013fb556ff84a96c7918a9bc03ab6018..22ea7c770fac4cbc569923fcfd537f900027f05f 100644 (file)
@@ -41,7 +41,7 @@ env = Environment(BUILDERS = {'MyAction' :
 env.MyAction('target', [])
 """)
 
-expect = "scons: *** Calling Configure from Builders is not supported.\n"
+expect = "scons: *** [target] Calling Configure from Builders is not supported.\n"
 
 test.run(status=2, stderr=expect)
 
index 710c8197679e78f5906672e75c4af1dd938d53e3..8485ce5d23bcb2abc430bd3e1d850415b46040fd 100644 (file)
@@ -53,7 +53,7 @@ test.write('exit.in', 'exit\n')
 # no longer exists or that line in the source file no longer exists,
 # so make sure the proper variations are supported in the following
 # regexp.
-expect = """scons: \*\*\* \[exit.out\] Exception
+expect = """scons: \*\*\* \[exit.out\] Exception : exit
 Traceback \((most recent call|innermost) last\):
 (  File ".+", line \d+, in \S+
     [^\n]+
index c8205ed1dcf38d63423708e12b6c3a2b86a6ab05..b5d8e44bcc438b381a067287b82afbc772033e30 100644 (file)
@@ -31,6 +31,7 @@ attributes we expect to be most commonly used.
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import TestSCons
+import re
 
 _python_ = TestSCons._python_
 
@@ -59,19 +60,43 @@ test.write('mypass.py', contents)
 test.write('myfail.py', contents)
 
 test.write('SConstruct', """\
-Command('f3', 'f3.in', r'@%(_python_)s mypass.py -  f3 $TARGET $SOURCE')
-Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
-Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
-Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 -  $TARGET $SOURCE')
+Command('f03', 'f03.in', r'@%(_python_)s mypass.py -   f03 $TARGET $SOURCE')
+Command('f04', 'f04.in', r'@%(_python_)s myfail.py f03 f04 $TARGET $SOURCE')
+Command('f05', 'f05.in', r'@%(_python_)s myfail.py f04 f05 $TARGET $SOURCE')
+Command('f06', 'f06.in', r'@%(_python_)s mypass.py f05 -   $TARGET $SOURCE')
+Command('f07', 'f07.in', r'@%(_python_)s mypass.py f07 -   $TARGET $SOURCE')
+
+import SCons.Errors
+def raiseExcAction(exc):
+    def action(env, target, source):
+        raise exc
+    return action
+def returnExcAction(exc):
+    def action(env, target, source):
+        return exc
+    return action
+class MyBuildError(SCons.Errors.BuildError):
+   pass
+
+Command('f08', 'f08.in', raiseExcAction(SCons.Errors.UserError("My User Error")))
+Command('f09', 'f09.in', returnExcAction(SCons.Errors.UserError("My User Error")))
+Command('f10', 'f10.in', raiseExcAction(MyBuildError(errstr="My Build Error", status=7)))
+Command('f11', 'f11.in', returnExcAction(MyBuildError(errstr="My Build Error", status=7)))
+Command('f12', 'f12.in', raiseExcAction(EnvironmentError(123, "My EnvironmentError", "f12")))
+Command('f13', 'f13.in', returnExcAction(EnvironmentError(123, "My EnvironmentError", "f13")))
+Command('f14', 'f14.in', raiseExcAction(SCons.Errors.InternalError("My InternalError")))
+Command('f15', 'f15.in', returnExcAction(SCons.Errors.InternalError("My InternalError")))
 
 def print_build_failures():
     from SCons.Script import GetBuildFailures
     import string
     bf_list = GetBuildFailures()
-    bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+    bf_list.sort(lambda a,b: cmp(str(a.node), str(b.node)))
     for bf in bf_list:
-        print "%%s failed (%%s):  %%s" %% (bf.node, bf.status, bf.errstr)
-        print "    %%s" %% string.join(bf.command)
+        assert( isinstance(bf, SCons.Errors.BuildError) )
+        print "BF: %%s failed (%%s):  %%s" %% (bf.node, bf.status, bf.errstr)
+        if bf.command:
+            print "BF:    %%s" %% string.join(Flatten(bf.command))
 
 try:
     import atexit
@@ -82,22 +107,31 @@ else:
     atexit.register(print_build_failures)
 """ % locals())
 
-test.write('f3.in', "f3.in\n")
-test.write('f4.in', "f4.in\n")
-test.write('f5.in', "f5.in\n")
-test.write('f6.in', "f6.in\n")
+test.write('f03.in', "f03.in\n")
+test.write('f04.in', "f04.in\n")
+test.write('f05.in', "f05.in\n")
+test.write('f06.in', "f06.in\n")
+# f07.in is intentionally missing...
+test.write('f08.in', "f08.in\n")
+test.write('f09.in', "f09.in\n")
+test.write('f10.in', "f10.in\n")
+test.write('f11.in', "f11.in\n")
+test.write('f12.in', "f12.in\n")
+test.write('f13.in', "f13.in\n")
+test.write('f14.in', "f14.in\n")
+test.write('f15.in', "f15.in\n")
 
 expect_stdout = """\
 scons: Reading SConscript files ...
 scons: done reading SConscript files.
 scons: Building targets ...
 scons: building terminated because of errors.
-f4 failed (1):  Error 1
-    %(_python_)s myfail.py f3 f4 "f4" "f4.in"
+BF: f04 failed (1):  Error 1
+BF:    %(_python_)s myfail.py f03 f04 "f04" "f04.in"
 """ % locals()
 
 expect_stderr = """\
-scons: *** [f4] Error 1
+scons: *** [f04] Error 1
 """
 
 test.run(arguments = '.',
@@ -105,11 +139,101 @@ test.run(arguments = '.',
          stdout = expect_stdout,
          stderr = expect_stderr)
 
-test.must_match(test.workpath('f3'), 'f3.in\n')
-test.must_not_exist(test.workpath('f4'))
-test.must_not_exist(test.workpath('f5'))
-test.must_not_exist(test.workpath('f6'))
+test.must_match(test.workpath('f03'), 'f03.in\n')
+test.must_not_exist(test.workpath('f04'))
+test.must_not_exist(test.workpath('f05'))
+test.must_not_exist(test.workpath('f06'))
+test.must_not_exist(test.workpath('f07'))
+test.must_not_exist(test.workpath('f08'))
+test.must_not_exist(test.workpath('f09'))
+test.must_not_exist(test.workpath('f10'))
+test.must_not_exist(test.workpath('f11'))
+test.must_not_exist(test.workpath('f12'))
+test.must_not_exist(test.workpath('f13'))
+test.must_not_exist(test.workpath('f14'))
+test.must_not_exist(test.workpath('f15'))
+
+
+expect_stdout = re.escape("""\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+action(["f08"], ["f08.in"])
+action(["f09"], ["f09.in"])
+action(["f10"], ["f10.in"])
+action(["f11"], ["f11.in"])
+action(["f12"], ["f12.in"])
+action(["f13"], ["f13.in"])
+action(["f14"], ["f14.in"])
+action(["f15"], ["f15.in"])
+scons: done building targets (errors occurred during build).
+BF: f04 failed (1):  Error 1
+BF:    %(_python_)s myfail.py f03 f04 "f04" "f04.in"
+BF: f05 failed (1):  Error 1
+BF:    %(_python_)s myfail.py f04 f05 "f05" "f05.in"
+BF: f07 failed (2):  Source `f07.in' not found, needed by target `f07'.
+BF: f08 failed (2):  My User Error
+BF:    action(["f08"], ["f08.in"])
+BF: f09 failed (2):  My User Error
+BF:    action(["f09"], ["f09.in"])
+BF: f10 failed (7):  My Build Error
+BF:    action(["f10"], ["f10.in"])
+BF: f11 failed (7):  My Build Error
+BF:    action(["f11"], ["f11.in"])
+BF: f12 failed (123):  My EnvironmentError
+BF:    action(["f12"], ["f12.in"])
+BF: f13 failed (123):  My EnvironmentError
+BF:    action(["f13"], ["f13.in"])
+BF: f14 failed (2):  InternalError : My InternalError
+BF:    action(["f14"], ["f14.in"])
+BF: f15 failed (2):  InternalError : My InternalError
+BF:    action(["f15"], ["f15.in"])
+""" % locals())
 
+expect_stderr = re.escape("""\
+scons: *** [f04] Error 1
+scons: *** [f05] Error 1
+scons: *** [f07] Source `f07.in' not found, needed by target `f07'.
+scons: *** [f08] My User Error
+scons: *** [f09] My User Error
+scons: *** [f10] My Build Error
+scons: *** [f11] My Build Error
+scons: *** [f12] f12: My EnvironmentError
+scons: *** [f13] f13: My EnvironmentError
+scons: *** [f14] InternalError : My InternalError
+""") + \
+"""\
+Traceback \((most recent call|innermost) last\):
+(  File ".+", line \d+, in \S+
+    [^\n]+
+)*(  File ".+", line \d+, in \S+
+)*(  File ".+", line \d+, in \S+
+    [^\n]+
+)*\S.+
+""" + \
+re.escape("""\
+scons: *** [f15] InternalError : My InternalError
+""")
+
+test.run(arguments = '-k .',
+         status = 2,
+         stdout = expect_stdout,
+         stderr = expect_stderr,
+         match = TestSCons.match_re_dotall)
+
+test.must_match(test.workpath('f03'), 'f03.in\n')
+test.must_not_exist(test.workpath('f04'))
+test.must_not_exist(test.workpath('f05'))
+test.must_match(test.workpath('f06'), 'f06.in\n')
+test.must_not_exist(test.workpath('f07'))
+test.must_not_exist(test.workpath('f08'))
+test.must_not_exist(test.workpath('f09'))
+test.must_not_exist(test.workpath('f10'))
+test.must_not_exist(test.workpath('f11'))
+test.must_not_exist(test.workpath('f12'))
+test.must_not_exist(test.workpath('f13'))
+test.must_not_exist(test.workpath('f14'))
+test.must_not_exist(test.workpath('f15'))
 
 
 test.pass_test()
index f8447ca357812574e7d8524e1ac44bfd625ee2ce..554f8dfa6cdd14d0ff49613ed534c1ca8bbc907b 100644 (file)
@@ -213,13 +213,13 @@ if sys.platform == 'win32' or string.find(sys.platform, 'irix') != -1:
     test.run(arguments = '-f SConstructFoo')
 else:
     test.run(arguments = '-f SConstructFoo', status=2, stderr='''\
-scons: \*\*\* Source file: foo\..* is static and is not compatible with shared target: .*
+scons: \*\*\* \[.*\] Source file: foo\..* is static and is not compatible with shared target: .*
 ''',
     match=TestSCons.match_re_dotall)
     # Run it again to make sure that we still get the error
     # even though the static objects already exist.
     test.run(arguments = '-f SConstructFoo', status=2, stderr='''\
-scons: \*\*\* Source file: foo\..* is static and is not compatible with shared target: .*
+scons: \*\*\* \[.*\] Source file: foo\..* is static and is not compatible with shared target: .*
 ''',
     match=TestSCons.match_re_dotall)
 
index d9c414ff806859a232dee2b237fe9144878cbab7..cfcad736acc8dff6284ef8db88a0eb8a4d7aea4b 100644 (file)
@@ -143,7 +143,7 @@ Default(all)
 
 re_error = """\
 (scons: \\*\\*\\* \\[failed\\d+] Error 2\\n)|\
-(scons: \\*\\*\\* Source `MissingSrc' not found, needed by target `missing\\d+'\\.(  Stop\\.)?\\n)|\
+(scons: \\*\\*\\* \\[missing\\d+] Source `MissingSrc' not found, needed by target `missing\\d+'\\.(  Stop\\.)?\\n)|\
 (scons: \\*\\*\\* \\[\\w+] Build interrupted\.\\n)\
 """
 
index 07cb1707d627604f2fb644d5a72534271e8401df..99ba7edf50b3e54ed645dbdf07b157467ca2a1ae 100644 (file)
@@ -122,7 +122,7 @@ expect = """\
 
 scons: warning: Ignoring missing SConscript '%(sub_SConscript)s'
 %(SConstruct_file_line)s
-scons: *** Source `aaa.in' not found, needed by target `aaa.out'.  Stop.
+scons: *** [aaa.out] Source `aaa.in' not found, needed by target `aaa.out'.
 """ % locals()
 
 test.run(status=2, stderr=expect)
index d8204dbaf96d94d3f2650b99a4127a4c050364c5..5d10fd735e957d13fe963659632af407935ef393 100644 (file)
@@ -109,7 +109,7 @@ test.write('zzz', "zzz 1\n")
 test.run(arguments = '.',
          status = 2,
          stderr = """\
-scons: *** kfile_scan error:  yyy 1
+scons: *** [foo] Exception : kfile_scan error:  yyy 1
 """)
 
 test.pass_test()
index 28c9a0ae5cf05a123a4d1a234a4e3c0b20a475e1..deb52a07f5a2bcf8404218c3a53a7a8f28d8574b 100644 (file)
@@ -181,5 +181,31 @@ test.fail_test(string.find(err, 'Exception') != -1 or \
                string.find(err, 'Traceback') != -1)
 
 
+# Test SConscript with errors and an atexit function.
+# Should not give traceback; the task error should get converted
+# to a BuildError.
+test.write('SConstruct', """
+import atexit
+
+env = Environment()
+env2 = env.Clone()
+
+env.Install("target", "dir1/myFile")
+env2.Install("target", "dir2/myFile")
+
+def print_build_failures():
+    from SCons.Script import GetBuildFailures
+    for bf in GetBuildFailures():
+       print bf.action
+
+atexit.register(print_build_failures)
+""")
+
+test.run(status=2, stderr=None)
+err = test.stderr()
+test.fail_test(string.find(err, 'Exception') != -1 or \
+               string.find(err, 'Traceback') != -1)
+
+
 # No tests failed; OK.
 test.pass_test()
index b0939c0cb14fe91aaaeaf60c2a3134ba26f75ced..98e3e834db30a25b6b137bd2a8debb2b55dd4c63 100644 (file)
@@ -47,7 +47,7 @@ env.B(target = 'foo.out', source = 'foo.in')
 
 test.write('foo.in', "foo.in\n")
 
-expected_stderr = """scons: \*\*\* \[foo.out\] Exception
+expected_stderr = """scons: \*\*\* \[foo.out\] Exception : func exception
 Traceback \((most recent call|innermost) last\):
 (  File ".+", line \d+, in \S+
     [^\n]+
@@ -109,7 +109,7 @@ test.run(arguments = '.', status = 2, stderr = expected_stderr)
 
 expected_stderr_list = [
     "scons: *** [out.f1] Error 1\n",
-    "scons: *** Source `in.f2' not found, needed by target `out.f2'.\n",
+    "scons: *** [out.f2] Source `in.f2' not found, needed by target `out.f2'.\n",
     "scons: *** [out.f3] Error 1\n",
 ]
 
index 3e47cbe675b97681c35e5475e352fcc341ae41b7..b130a1a12fc45360fb843def6a80b1b85509b0e3 100644 (file)
@@ -46,20 +46,21 @@ Dir('ddd')
 """)
 
 test.run(arguments = 'foo',
-         stderr = "scons: *** Do not know how to make target `foo'.  Stop.\n",
-         status = 2)
+         stderr = "scons: \\*\\*\\* Do not know how to make target `foo'.( *Stop.)?\n",
+         status = 2,
+         match=TestSCons.match_re_dotall)
 
 test.run(arguments = '-k foo/bar foo',
          stderr = "scons: *** Do not know how to make target `%s'.\n" % foo_bar,
          status = 2)
 
 test.run(arguments = "aaa.out",
-         stderr = "scons: *** Source `aaa.in' not found, needed by target `aaa.out'.  Stop.\n",
+         stderr = "scons: *** [aaa.out] Source `aaa.in' not found, needed by target `aaa.out'.\n",
          status = 2)
 
 test.run(arguments = "-k bbb.out aaa.out",
-         stderr = """scons: *** Source `bbb.in' not found, needed by target `bbb.out'.
-scons: *** Source `aaa.in' not found, needed by target `aaa.out'.
+         stderr = """scons: *** [bbb.out] Source `bbb.in' not found, needed by target `bbb.out'.
+scons: *** [aaa.out] Source `aaa.in' not found, needed by target `aaa.out'.
 """,
          status = 2)
 
@@ -71,8 +72,9 @@ scons: *** Do not know how to make target `bbb.in'.
 
 
 test.run(arguments = 'xxx',
-         stderr = "scons: *** Do not know how to make target `xxx'.  Stop.\n",
-         status = 2)
+         stderr = "scons: \\*\\*\\* Do not know how to make target `xxx'.( *Stop.)?\n",
+         status = 2,
+         match=TestSCons.match_re_dotall)
 
 test.run(arguments = 'ddd')
 
index cf7b81d2815cf858b82a590d5199a540e8d46ab7..93cb20645dedbcb1a4ab0a58af8edadf9061fcd7 100644 (file)
@@ -65,14 +65,14 @@ env.Command('foo', 'foo.k', Copy('$TARGET', '$SOURCE'))
 
 test.write('foo.k', "foo.k\n")
 
-test.run(status = 2, stderr = "scons: *** kfile_scan error\n")
+test.run(status = 2, stderr = "scons: *** [foo] Exception : kfile_scan error\n")
 
 test.run(arguments = "--debug=stacktrace",
          status = 2,
          stderr = None)
 
 lines = [
-    "scons: *** kfile_scan error",
+    "scons: *** [foo] Exception : kfile_scan error",
     "scons: internal stack trace:",
     'raise Exception, "kfile_scan error"',
 ]
index 61ef07d8f5508277f6cad1e6798df5f3625fc58d..9c9d93a1f3b69b51f7ae5fdabee5aa17e792b911 100644 (file)
@@ -51,9 +51,10 @@ test.write('foo.c', """\
 test.symlink('nonexistent', 'foo.h')
 
 expect = """\
-scons: *** Implicit dependency `foo.h' not found, needed by target `%s'.  Stop.
+scons: \\*\\*\\* \\[foo.o(bj)?\\] Implicit dependency `foo.h' not found, needed by target `%s'.(  Stop.)?
 """% foo_obj
 
-test.run(arguments = '.', status = 2, stderr = expect)
+test.run(arguments = '.', status = 2, stderr = expect, 
+         match=TestSCons.match_re_dotall)
 
 test.pass_test()
index e242e61e7f886448066391283c482adf0741f5f0..366b7426c159e57a8c5868aa09e0394049f2a011 100644 (file)
@@ -45,7 +45,7 @@ Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
 test.symlink('nonexistent', 'file.in')
 
 expect = """\
-scons: *** Source `file.in' not found, needed by target `file.out'.  Stop.
+scons: *** [file.out] Source `file.in' not found, needed by target `file.out'.
 """
 
 test.run(arguments = '.', status = 2, stderr = expect)