From bc08cde50aee2b6a495506819b97014fdb880058 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 3 Mar 2003 06:28:49 +0000 Subject: Don't eat scanner exceptions. --- src/CHANGES.txt | 3 + src/engine/SCons/Script/__init__.py | 2 + src/engine/SCons/Taskmaster.py | 39 ++++++++----- src/engine/SCons/TaskmasterTests.py | 45 +++++++++++++-- test/Scanner-exception.py | 112 ++++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 test/Scanner-exception.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 22f0bdc..f4fa6ac 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -51,6 +51,9 @@ RELEASE 0.12 - XXX (e.g. ${SOURCES[2]}) when calculating the build signature of a command. + - Don't silently swallow exceptions thrown by Scanners (or other + exceptions while finding a node's dependent children). + From Lachlan O'Dea: - Add SharedObject() support to the masm tool. diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 3716d2e..b14e50d 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -143,6 +143,8 @@ class BuildTask(SCons.Taskmaster.Task): s = s + ' Stop.' sys.stderr.write("scons: *** %s\n" % s) else: + if e is None: + e = sys.exc_type sys.stderr.write("scons: *** %s\n" % e) self.do_failed() diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 521dc19..68e251c 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -73,13 +73,12 @@ class Task: This method is called from multiple threads in a parallel build, so only do thread safe stuff here. Do thread unsafe stuff in prepare(), executed() or failed().""" - try: - # We recorded an exception while getting this Task ready - # for execution. Raise it now. - raise self.node.exc_type, self.node.exc_value - except AttributeError: - # The normal case: no exception to raise. - pass + + # Now that it's the appropriate time, give the TaskMaster a + # chance to raise any exceptions it encountered while preparing + # this task. + self.tm.exception_raise() + try: self.targets[0].build() except KeyboardInterrupt: @@ -194,6 +193,7 @@ class Taskmaster: self.ready = None # the next task that is ready to be executed self.calc = calc self.order = order + self.exception_set(None, None) def _find_next_ready_node(self): """Find the next node that is ready to be built""" @@ -218,10 +218,9 @@ class Taskmaster: except: # We had a problem just trying to figure out the # children (like a child couldn't be linked in to a - # BuildDir). Arrange to raise the exception when the - # Task is "executed." - node.exc_type = sys.exc_type - node.exc_value = sys.exc_value + # BuildDir, or a Scanner threw something). Arrange to + # raise the exception when the Task is "executed." + self.exception_set(sys.exc_type, sys.exc_value) self.candidates.pop() self.ready = node break @@ -296,8 +295,7 @@ class Taskmaster: # a child couldn't be linked in to a BuildDir when deciding # whether this node is current). Arrange to raise the # exception when the Task is "executed." - node.exc_type = sys.exc_type - node.exc_value = sys.exc_value + self.exception_set(sys.exc_type, sys.exc_value) self.ready = None return task @@ -332,3 +330,18 @@ class Taskmaster: self.candidates.extend(self.pending) self.pending = [] + def exception_set(self, type, value): + """Record an exception type and value to raise later, at an + appropriate time.""" + self.exc_type = type + self.exc_value = value + + def exception_raise(self): + """Raise any pending exception that was recorded while + getting a Task ready for execution.""" + if self.exc_type: + try: + raise self.exc_type, self.exc_value + finally: + self.exception_set(None, None) + diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index 5a19c56..2f9383a 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -385,8 +385,8 @@ class TaskmasterTestCase(unittest.TestCase): n1 = Node("n1") tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask) t = tm.next_task() - assert n1.exc_type == MyException, n1.exc_type - assert str(n1.exc_value) == "from make_ready()", n1.exc_value + assert tm.exc_type == MyException, tm.exc_type + assert str(tm.exc_value) == "from make_ready()", tm.exc_value def test_children_errors(self): @@ -398,8 +398,8 @@ class TaskmasterTestCase(unittest.TestCase): n1 = MyNode("n1") tm = SCons.Taskmaster.Taskmaster([n1]) t = tm.next_task() - assert n1.exc_type == SCons.Errors.StopError, "Did not record StopError on node" - assert str(n1.exc_value) == "stop!", "Unexpected exc_value `%s'" % n1.exc_value + assert tm.exc_type == SCons.Errors.StopError, "Did not record StopError on node" + assert str(tm.exc_value) == "stop!", "Unexpected exc_value `%s'" % tm.exc_value def test_cycle_detection(self): """Test detecting dependency cycles @@ -590,9 +590,9 @@ class TaskmasterTestCase(unittest.TestCase): built_text = None n5 = Node("n5") - n5.exc_type = MyException - n5.exc_value = "exception value" tm = SCons.Taskmaster.Taskmaster([n5]) + tm.exc_type = MyException + tm.exc_value = "exception value" t = tm.next_task() exc_caught = None try: @@ -603,6 +603,39 @@ class TaskmasterTestCase(unittest.TestCase): assert exc_caught, "did not catch expected MyException" assert built_text is None, built_text + def test_exception(self): + """Test generic Taskmaster exception handling + + """ + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + + tm.exception_set(1, 2) + assert tm.exc_type == 1, tm.exc_type + assert tm.exc_value == 2, tm.exc_value + + tm.exception_set(None, None) + assert tm.exc_type is None, tm.exc_type + assert tm.exc_value is None, tm.exc_value + + tm.exception_set("exception 1", None) + try: + tm.exception_raise() + except: + assert sys.exc_type == "exception 1", sys.exc_type + assert sys.exc_value is None, sys.exc_type + else: + assert 0, "did not catch expected exception" + + tm.exception_set("exception 2", "xyzzy") + try: + tm.exception_raise() + except: + assert sys.exc_type == "exception 2", sys.exc_type + assert sys.exc_value == "xyzzy", sys.exc_type + else: + assert 0, "did not catch expected exception" + if __name__ == "__main__": diff --git a/test/Scanner-exception.py b/test/Scanner-exception.py new file mode 100644 index 0000000..8fe7875 --- /dev/null +++ b/test/Scanner-exception.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import TestSCons + +python = TestSCons.python + +test = TestSCons.TestSCons() + +# Execute a subsidiary SConscript just to make sure we can +# get at the SCanners keyword from there. + +test.write('SConstruct', """ +SConscript('SConscript') +""") + +test.write('SConscript', """ +import re + +include_re = re.compile(r'^include\s+(\S+)$', re.M) +exception_re = re.compile(r'^exception\s+(.+)$', re.M) + +def kfile_scan(node, env, target, arg): + contents = node.get_contents() + exceptions = exception_re.findall(contents) + if exceptions: + raise "kfile_scan error: %s" % exceptions[0] + includes = include_re.findall(contents) + return includes + +kscan = Scanner(name = 'kfile', + function = kfile_scan, + argument = None, + recursive = 1, + skeys = ['.k']) + +def process(outf, inf): + for line in inf.readlines(): + if line[:8] == 'include ': + file = line[8:-1] + process(outf, open(file, 'rb')) + else: + outf.write(line) + +def cat(env, source, target): + target = str(target[0]) + source = map(str, source) + + outf = open(target, 'wb') + for src in source: + process(outf, open(src, 'rb')) + outf.close() + +env = Environment(BUILDERS={'Cat':Builder(action=cat)}) +env.Append(SCANNERS = [kscan]) + +env.Cat('foo', 'foo.k') + +bar_in = File('bar.in') +env.Cat('bar', bar_in) +bar_in.source_scanner = kscan +""") + +test.write('foo.k', +"""foo.k 1 line 1 +include xxx +include yyy +foo.k 1 line 4 +""") + +test.write('bar.in', +"""include yyy +bar.in 1 line 2 +bar.in 1 line 3 +include zzz +""") + +test.write('xxx', "xxx 1\n") + +test.write('yyy', "exception yyy 1\n") + +test.write('zzz', "zzz 1\n") + +test.run(arguments = '.', + status = 2, + stderr = "scons: *** kfile_scan error: yyy 1\n") + +test.pass_test() -- cgit v0.12