From 321aa365f5770b70be13631f717be8ecfeaf70f2 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Tue, 2 Oct 2001 03:15:32 +0000 Subject: Handle build errors. --- src/engine/SCons/Builder.py | 15 +++++-- src/engine/SCons/BuilderTests.py | 35 ++++++++++------ src/engine/SCons/Errors.py | 6 +++ src/engine/SCons/ErrorsTests.py | 8 ++++ src/engine/SCons/Node/FSTests.py | 5 +++ src/engine/SCons/Node/NodeTests.py | 19 +++++++++ src/engine/SCons/Node/__init__.py | 8 +++- src/script/scons.py | 8 +++- test/builderrors.py | 84 ++++++++++++++++++++++++++++++++++++++ test/multiline.py | 1 + 10 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 test/builderrors.py diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index d354c57..c90bc40 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -55,7 +55,7 @@ class Builder: def execute(self, **kw): """Execute a builder's action to create an output object. """ - apply(self.action.execute, (), kw) + return apply(self.action.execute, (), kw) @@ -99,6 +99,7 @@ class CommandAction(ActionBase): cmd = self.command % kw if print_actions: self.show(cmd) + ret = 0 if execute_actions: pid = os.fork() if not pid: @@ -112,7 +113,10 @@ class CommandAction(ActionBase): os.execvpe(args[0], args, ENV) else: # Parent process. - os.waitpid(pid, 0) + pid, stat = os.waitpid(pid, 0) + ret = stat >> 8 + return ret + class FunctionAction(ActionBase): @@ -124,7 +128,7 @@ class FunctionAction(ActionBase): # if print_actions: # XXX: WHAT SHOULD WE PRINT HERE? if execute_actions: - self.function(kw) + return self.function(kw) class ListAction(ActionBase): """Class for lists of other actions.""" @@ -133,4 +137,7 @@ class ListAction(ActionBase): def execute(self, **kw): for l in self.list: - apply(l.execute, (), kw) + r = apply(l.execute, (), kw) + if r != 0: + return r + return 0 diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 0ea81f0..965df6e 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -77,23 +77,34 @@ class BuilderTestCase(unittest.TestCase): """Test execution of simple Builder objects One Builder is a string that executes an external command, - and one is an internal Python function. + one is an internal Python function, one is a list + containing one of each. """ - cmd = "python %s %s xyzzy" % (act_py, outfile) - builder = SCons.Builder.Builder(action = cmd) - builder.execute() + cmd1 = "python %s %s xyzzy" % (act_py, outfile) + builder = SCons.Builder.Builder(action = cmd1) + r = builder.execute() + assert r == 0 assert test.read(outfile, 'r') == "act.py: xyzzy\n" - def function(kw): - import os, string, sys + def function1(kw): f = open(kw['out'], 'w') - f.write("function\n") + f.write("function1\n") f.close() - return not None - - builder = SCons.Builder.Builder(action = function) - builder.execute(out = outfile) - assert test.read(outfile, 'r') == "function\n" + return 1 + + builder = SCons.Builder.Builder(action = function1) + r = builder.execute(out = outfile) + assert r == 1 + assert test.read(outfile, 'r') == "function1\n" + + cmd2 = "python %s %s syzygy" % (act_py, outfile) + def function2(kw): + open(kw['out'], 'a').write("function2\n") + return 2 + builder = SCons.Builder.Builder(action = [cmd2, function2]) + r = builder.execute(out = outfile) + assert r == 2 + assert test.read(outfile, 'r') == "act.py: syzygy\nfunction2\n" def test_insuffix(self): """Test Builder creation with a specified input suffix diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py index ac43837..8599aee 100644 --- a/src/engine/SCons/Errors.py +++ b/src/engine/SCons/Errors.py @@ -9,6 +9,12 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +class BuildError(Exception): + def __init__(self, node=None, stat=0, *args): + self.node = node + self.stat = stat + self.args = args + class InternalError(Exception): def __init__(self, args=None): self.args = args diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py index d39a287..4747a43 100644 --- a/src/engine/SCons/ErrorsTests.py +++ b/src/engine/SCons/ErrorsTests.py @@ -6,6 +6,14 @@ import SCons.Errors class ErrorsTestCase(unittest.TestCase): + def test_BuildError(self): + """Test the BuildError exception.""" + try: + raise SCons.Errors.BuildError(node = "n", stat = 7) + except SCons.Errors.BuildError, e: + assert e.node == "n" + assert e.stat == 7 + def test_InternalError(self): """Test the InternalError exception.""" try: diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 8e7e2a6..1cffd8c 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -6,17 +6,22 @@ import unittest import SCons.Node.FS + + built_it = None class Builder: def execute(self, **kw): global built_it built_it = 1 + return 0 class Environment: def Dictionary(self, *args): pass + + class FSTestCase(unittest.TestCase): def runTest(self): """Test FS (file system) Node operations diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 738c7cc..26cf6bd 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -4,6 +4,7 @@ import os import sys import unittest +import SCons.Errors import SCons.Node @@ -14,6 +15,11 @@ class Builder: def execute(self, **kw): global built_it built_it = 1 + return 0 + +class FailBuilder: + def execute(self, **kw): + return 1 class Environment: def Dictionary(self, *args): @@ -23,6 +29,19 @@ class Environment: class NodeTestCase(unittest.TestCase): + def test_BuildException(self): + """Test throwing an exception on build failure. + """ + node = SCons.Node.Node() + node.builder_set(FailBuilder()) + node.env_set(Environment()) + try: + node.build() + except SCons.Errors.BuildError: + pass + else: + raise TestFailed, "did not catch expected BuildError" + def test_build(self): """Test building a node """ diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 4f414ec..de15712 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -8,6 +8,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +from SCons.Errors import BuildError import string @@ -25,8 +26,11 @@ class Node: def build(self): sources_str = string.join(map(lambda x: str(x), self.sources)) - self.builder.execute(ENV = self.env.Dictionary('ENV'), - target = str(self), source = sources_str) + stat = self.builder.execute(ENV = self.env.Dictionary('ENV'), + target = str(self), source = sources_str) + if stat != 0: + raise BuildError(node = self, stat = stat) + return stat def builder_set(self, builder): self.builder = builder diff --git a/src/script/scons.py b/src/script/scons.py index fbb7341..e0e958b 100644 --- a/src/script/scons.py +++ b/src/script/scons.py @@ -32,7 +32,11 @@ class Task: self.target = target def execute(self): - self.target.build() + try: + self.target.build() + except BuildError, e: + sys.stderr.write("scons: *** [%s] Error %d\n" % (e.node, e.stat)) + raise @@ -62,7 +66,7 @@ class Taskmaster: pass def failed(self, task): - pass + self.num_iterated = len(self.targets) # Global variables diff --git a/test/builderrors.py b/test/builderrors.py new file mode 100644 index 0000000..d11ab24 --- /dev/null +++ b/test/builderrors.py @@ -0,0 +1,84 @@ + +#!/usr/bin/env python + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os +import TestSCons + +test = TestSCons.TestSCons() + +test.subdir('one', 'two', 'three') + +test.write('build.py', r""" +import sys +exitval = int(sys.argv[1]) +if exitval == 0: + contents = open(sys.argv[3], 'r').read() + file = open(sys.argv[2], 'w') + file.write(contents) + file.close() +sys.exit(exitval) +""") + +test.write(['one', 'SConstruct'], """ +B0 = Builder(name = 'B0', action = "python ../build.py 0 %(target)s %(source)s") +B1 = Builder(name = 'B1', action = "python ../build.py 1 %(target)s %(source)s") +env = Environment(BUILDERS = [B0, B1]) +env.B1(target = 'f1.out', source = 'f1.in') +env.B0(target = 'f2.out', source = 'f2.in') +env.B0(target = 'f3.out', source = 'f3.in') +""") + +test.write(['one', 'f1.in'], "one/f1.in\n") +test.write(['one', 'f2.in'], "one/f2.in\n") +test.write(['one', 'f3.in'], "one/f3.in\n") + +test.run(chdir = 'one', arguments = "f1.out f2.out f3.out", + stderr = "scons: *** [f1.out] Error 1\n") + +test.fail_test(os.path.exists(test.workpath('f1.out'))) +test.fail_test(os.path.exists(test.workpath('f2.out'))) +test.fail_test(os.path.exists(test.workpath('f3.out'))) + +test.write(['two', 'SConstruct'], """ +B0 = Builder(name = 'B0', action = "python ../build.py 0 %(target)s %(source)s") +B1 = Builder(name = 'B1', action = "python ../build.py 1 %(target)s %(source)s") +env = Environment(BUILDERS = [B0, B1]) +env.B0(target = 'f1.out', source = 'f1.in') +env.B1(target = 'f2.out', source = 'f2.in') +env.B0(target = 'f3.out', source = 'f3.in') +""") + +test.write(['two', 'f1.in'], "two/f1.in\n") +test.write(['two', 'f2.in'], "two/f2.in\n") +test.write(['two', 'f3.in'], "two/f3.in\n") + +test.run(chdir = 'two', arguments = "f1.out f2.out f3.out", + stderr = "scons: *** [f2.out] Error 1\n") + +test.fail_test(test.read(['two', 'f1.out']) != "two/f1.in\n") +test.fail_test(os.path.exists(test.workpath('f2.out'))) +test.fail_test(os.path.exists(test.workpath('f3.out'))) + +test.write(['three', 'SConstruct'], """ +B0 = Builder(name = 'B0', action = "python ../build.py 0 %(target)s %(source)s") +B1 = Builder(name = 'B1', action = "python ../build.py 1 %(target)s %(source)s") +env = Environment(BUILDERS = [B0, B1]) +env.B0(target = 'f1.out', source = 'f1.in') +env.B0(target = 'f2.out', source = 'f2.in') +env.B1(target = 'f3.out', source = 'f3.in') +""") + +test.write(['three', 'f1.in'], "three/f1.in\n") +test.write(['three', 'f2.in'], "three/f2.in\n") +test.write(['three', 'f3.in'], "three/f3.in\n") + +test.run(chdir = 'three', arguments = "f1.out f2.out f3.out", + stderr = "scons: *** [f3.out] Error 1\n") + +test.fail_test(test.read(['three', 'f1.out']) != "three/f1.in\n") +test.fail_test(test.read(['three', 'f2.out']) != "three/f2.in\n") +test.fail_test(os.path.exists(test.workpath('f3.out'))) + +test.pass_test() diff --git a/test/multiline.py b/test/multiline.py index 5c51fac..7bdb586 100644 --- a/test/multiline.py +++ b/test/multiline.py @@ -13,6 +13,7 @@ contents = open(sys.argv[2], 'r').read() file = open(sys.argv[1], 'w') file.write(contents) file.close() +sys.exit(0) """) test.write('SConstruct', """ -- cgit v0.12