Commits

Anonymous committed 596a072

Handle build errors.

Comments (0)

Files changed (10)

src/engine/SCons/Builder.py

     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)
 
 
 
 	cmd = self.command % kw
 	if print_actions:
 	    self.show(cmd)
+	ret = 0
 	if execute_actions:
 	    pid = os.fork()
 	    if not pid:
 		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):
 	# 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."""
 
     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

src/engine/SCons/BuilderTests.py

 	"""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
+	    return 1
 
-	builder = SCons.Builder.Builder(action = function)
-	builder.execute(out = outfile)
-	assert test.read(outfile, 'r') == "function\n"
+	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

src/engine/SCons/Errors.py

 
 
 
+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

src/engine/SCons/ErrorsTests.py

 
 
 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:

src/engine/SCons/Node/FSTests.py

 
 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

src/engine/SCons/Node/NodeTests.py

 import sys
 import unittest
 
+import SCons.Errors
 import SCons.Node
 
 
     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):
 
 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
 	"""

src/engine/SCons/Node/__init__.py

 
 
 
+from SCons.Errors import BuildError
 import string
 
 
 
     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

src/script/scons.py

         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
 
 
 
         pass
 
     def failed(self, task):
-        pass
+        self.num_iterated = len(self.targets)
 
 
 # Global variables

test/builderrors.py

+
+#!/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()

test/multiline.py

 file = open(sys.argv[1], 'w')
 file.write(contents)
 file.close()
+sys.exit(0)
 """)
 
 test.write('SConstruct', """