From: stevenknight Date: Thu, 19 Dec 2002 15:31:04 +0000 (+0000) Subject: Finish the Action refactoring by removing Builder.execute() methods and moving except... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=8c3cb7d540bc51f2470eae116cd0a8dc06ac1c53;p=scons.git Finish the Action refactoring by removing Builder.execute() methods and moving exception handling into the TaskTask class. git-svn-id: http://scons.tigris.org/svn/scons/trunk@526 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index dfa41ada..2f262bf9 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -30,19 +30,73 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" def Func(): pass +import os import sys import types import unittest +import UserDict import SCons.Action -import TestCmd +import SCons.Environment import SCons.Errors -import UserDict +import TestCmd -import SCons.Environment -def Environment(dict): - return apply(SCons.Environment.Environment, (), dict) +# Initial setup of the common environment for all tests, +# a temporary working directory containing a +# script for writing arguments to an output file. +# +# We don't do this as a setUp() method because it's +# unnecessary to create a separate directory and script +# for each test, they can just use the one. +test = TestCmd.TestCmd(workdir = '') + +test.write('act.py', """import os, string, sys +f = open(sys.argv[1], 'w') +f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n") +try: + if sys.argv[3]: + f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n") +except: + pass +f.close() +sys.exit(0) +""") + +act_py = test.workpath('act.py') + +outfile = test.workpath('outfile') +outfile2 = test.workpath('outfile2') + +scons_env = SCons.Environment.Environment() + +class Environment: + def __init__(self, **kw): + self.d = {} + self.d['SHELL'] = scons_env['SHELL'] + self.d['SPAWN'] = scons_env['SPAWN'] + self.d['ESCAPE'] = scons_env['ESCAPE'] + for k, v in kw.items(): + self.d[k] = v + def subst(self, s): + if not SCons.Util.is_String(s): + return s + try: + if s[0] == '$': + return self.d.get(s[1:], '') + except IndexError: + pass + return self.d.get(s, s) + def __getitem__(self, item): + return self.d[item] + def has_key(self, item): + return self.d.has_key(item) + def get(self, key, value): + return self.d.get(key, value) + def items(self): + return self.d.items() + +python = sys.executable class ActionTestCase(unittest.TestCase): @@ -53,6 +107,7 @@ class ActionTestCase(unittest.TestCase): pass a1 = SCons.Action.Action(foo) assert isinstance(a1, SCons.Action.FunctionAction), a1 + assert a1.function == foo, a1.function a2 = SCons.Action.Action("string") assert isinstance(a2, SCons.Action.CommandAction), a2 @@ -96,6 +151,15 @@ class ActionTestCase(unittest.TestCase): assert isinstance(a9.list[2], SCons.Action.CommandAction), a9.list[2] assert a9.list[2].cmd_list == ["z"], a9.list[2].cmd_list + a10 = SCons.Action.Action(["x", foo, "z"]) + assert isinstance(a10, SCons.Action.ListAction), a10 + assert isinstance(a10.list[0], SCons.Action.CommandAction), a10.list[0] + assert a10.list[0].cmd_list == ["x"], a10.list[0].cmd_list + assert isinstance(a10.list[1], SCons.Action.FunctionAction), a10.list[1] + assert a10.list[1].function == foo, a10.list[1].function + assert isinstance(a10.list[2], SCons.Action.CommandAction), a10.list[2] + assert a10.list[2].cmd_list == ["z"], a10.list[2].cmd_list + class ActionBaseTestCase(unittest.TestCase): def test_cmp(self): @@ -120,17 +184,19 @@ class ActionBaseTestCase(unittest.TestCase): """ a = SCons.Action.Action("x") - d = a.subst_dict([],[],Environment({'a' : 'A', 'b' : 'B'})) + d = a.subst_dict([], [], Environment(a = 'A', b = 'B')) assert d['a'] == 'A', d assert d['b'] == 'B', d - d = a.subst_dict(target = 't', source = 's',env=Environment({})) + d = a.subst_dict(target = 't', source = 's', env = Environment()) assert str(d['TARGETS']) == 't', d['TARGETS'] assert str(d['TARGET']) == 't', d['TARGET'] assert str(d['SOURCES']) == 's', d['SOURCES'] assert str(d['SOURCE']) == 's', d['SOURCE'] - d = a.subst_dict(target = ['t1', 't2'], source = ['s1', 's2'], env=Environment({})) + d = a.subst_dict(target = ['t1', 't2'], + source = ['s1', 's2'], + env = Environment()) TARGETS = map(lambda x: str(x), d['TARGETS']) TARGETS.sort() assert TARGETS == ['t1', 't2'], d['TARGETS'] @@ -148,7 +214,9 @@ class ActionBaseTestCase(unittest.TestCase): def rstr(self): return 'rstr-' + self.name - d = a.subst_dict(target = [N('t3'), 't4'], source = ['s3', N('s4')], env=Environment({})) + d = a.subst_dict(target = [N('t3'), 't4'], + source = ['s3', N('s4')], + env = Environment()) TARGETS = map(lambda x: str(x), d['TARGETS']) TARGETS.sort() assert TARGETS == ['t3', 't4'], d['TARGETS'] @@ -165,10 +233,119 @@ class CommandActionTestCase(unittest.TestCase): assert a.cmd_list == [ "xyzzy" ], a.cmd_list def test_execute(self): - """Test executing a command Action + """Test execution of command Actions + """ - self.test_set_handler() - pass + cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd1) + r = act.execute([], [], Environment()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'xyzzy'\n", c + + cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd2) + r = act.execute('foo', [], Environment()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'foo'\n", c + + cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd3) + r = act.execute(['aaa', 'bbb'], [], Environment()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'aaa' 'bbb'\n", c + + cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd4) + r = act.execute([], ['one', 'two'], Environment()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'one' 'two'\n", c + + cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd4) + r = act.execute([], + source = ['three', 'four', 'five'], + env = Environment()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'three' 'four'\n", c + + cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd5) + r = act.execute(target = 'out5', + source = [], + env = Environment(ENV = {'XYZZY' : 'xyzzy'})) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c + + class Obj: + def __init__(self, str): + self._str = str + def __str__(self): + return self._str + + cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd6) + r = act.execute(target = [Obj('111'), Obj('222')], + source = [Obj('333'), Obj('444'), Obj('555')], + env = Environment()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: '222' '111' '333' '444'\n", c + + cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile, + python, act_py, outfile) + expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile, + python, act_py, outfile) + + act = SCons.Action.CommandAction(cmd7) + + global show_string + show_string = "" + def my_show(string): + global show_string + show_string = show_string + string + "\n" + act.show = my_show + + r = act.execute([], [], Environment()) + assert r == 0 + assert show_string == expect7, show_string + + if os.name == 'nt': + # NT treats execs of directories and non-executable files + # as "file not found" errors + expect_nonexistent = 1 + expect_nonexecutable = 1 + else: + expect_nonexistent = 127 + expect_nonexecutable = 126 + + # Test that a nonexistent command returns 127 + act = SCons.Action.CommandAction(python + "_XyZzY_") + r = act.execute([], [], Environment(out = outfile)) + assert r == expect_nonexistent, "r == %d" % r + + # Test that trying to execute a directory returns 126 + dir, tail = os.path.split(python) + act = SCons.Action.CommandAction(dir) + r = act.execute([], [], Environment(out = outfile)) + assert r == expect_nonexecutable, "r == %d" % r + + # Test that trying to execute a non-executable file returns 126 + act = SCons.Action.CommandAction(outfile) + r = act.execute([], [], Environment(out = outfile)) + assert r == expect_nonexecutable, "r == %d" % r def test_set_handler(self): """Test setting the command handler... @@ -200,16 +377,16 @@ class CommandActionTestCase(unittest.TestCase): assert 0, "should have gotten user error" a = SCons.Action.CommandAction(["xyzzy"]) - a.execute([],[],Environment({'SPAWN':func})) + a.execute([], [], Environment(SPAWN = func)) assert t.executed == [ 'xyzzy' ] a = SCons.Action.CommandAction(["xyzzy"]) - a.execute([],[],Environment({'SPAWN':func, 'SHELL':'fake shell'})) + a.execute([], [], Environment(SPAWN = func, SHELL = 'fake shell')) assert t.executed == [ 'xyzzy' ] assert t.shell == 'fake shell' a = SCons.Action.CommandAction([ LiteralStr("xyzzy") ]) - a.execute([],[],Environment({'SPAWN':func, 'ESCAPE':escape_func})) + a.execute([], [], Environment(SPAWN = func, ESCAPE = escape_func)) assert t.executed == [ '**xyzzy**' ], t.executed def test_get_raw_contents(self): @@ -218,7 +395,7 @@ class CommandActionTestCase(unittest.TestCase): a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", "$)", "|"]) c = a.get_raw_contents(target=[], source=[], - env=Environment({'foo':'FFF', 'bar':'BBB'})) + env=Environment(foo = 'FFF', bar = 'BBB')) assert c == "| $( FFF | BBB $) |", c def test_get_contents(self): @@ -227,7 +404,7 @@ class CommandActionTestCase(unittest.TestCase): a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", "$)", "|"]) c = a.get_contents(target=[], source=[], - env=Environment({'foo':'FFF', 'bar':'BBB'})) + env=Environment(foo = 'FFF', bar = 'BBB')) assert c == "| |", c class CommandGeneratorActionTestCase(unittest.TestCase): @@ -247,26 +424,13 @@ class CommandGeneratorActionTestCase(unittest.TestCase): def f(target, source, env, for_signature, self=self): dummy = env['dummy'] self.dummy = dummy - s = env.subst("$FOO $( bar $) baz") - assert s == 'foo baz\nbar ack bar baz', s - s = env.subst("$FOO $( bar $) baz", raw=1) - assert s == 'foo baz\nbar ack $( bar $) baz', s - s = env.subst_list("$FOO $( bar $) baz") - assert s == [[ 'foo', 'baz' ], [ 'bar', 'ack', 'bar', 'baz' ]], s - s = env.subst_list("$FOO $( bar $) baz", raw=1) - assert s == [[ 'foo', 'baz' ], - [ 'bar', 'ack', '$(', 'bar', '$)', 'baz' ]], s + s = env.subst("$FOO") + assert s == 'foo baz\nbar ack', s return "$FOO" def func_action(target, source, env, self=self): dummy=env['dummy'] - s = env.subst('$foo $( bar $)') - assert s == 'bar bar', s - s = env.subst('$foo $( bar $)', raw=1) - assert s == 'bar $( bar $)', s - s = env.subst_list([ '$foo', '$(', 'bar', '$)' ]) - assert s == [[ 'bar', 'bar' ]], s - s = env.subst_list([ '$foo', '$(', 'bar', '$)' ], raw=1) - assert s == [[ 'bar', '$(', 'bar', '$)' ]], s + s = env.subst('$foo') + assert s == 'bar', s self.dummy=dummy def f2(target, source, env, for_signature, f=func_action): return f @@ -278,17 +442,17 @@ class CommandGeneratorActionTestCase(unittest.TestCase): self.dummy = 0 self.cmd = [] self.args = [] - a.execute([],[],env=Environment({ 'FOO' : 'foo baz\nbar ack', - 'dummy' : 1, - 'SPAWN':ch})) + a.execute([], [], env=Environment(FOO = 'foo baz\nbar ack', + dummy = 1, + SPAWN = ch)) assert self.dummy == 1, self.dummy assert self.cmd == ['foo', 'bar'], self.cmd assert self.args == [[ 'foo', 'baz' ], [ 'bar', 'ack' ]], self.args - b=SCons.Action.CommandGeneratorAction(f2) + b = SCons.Action.CommandGeneratorAction(f2) self.dummy = 0 - b.execute(target=[], source=[], env=Environment({ 'foo' : 'bar', - 'dummy' : 2 })) + b.execute(target=[], source=[], env=Environment(foo = 'bar', + dummy = 2 )) assert self.dummy==2, self.dummy del self.dummy @@ -299,8 +463,8 @@ class CommandGeneratorActionTestCase(unittest.TestCase): self.t.rfile_called = 1 def f3(target, source, env, for_signature): return '' - c=SCons.Action.CommandGeneratorAction(f3) - c.execute(target=[], source=DummyFile(self), env=Environment({})) + c = SCons.Action.CommandGeneratorAction(f3) + c.execute(target=[], source=DummyFile(self), env=Environment()) assert self.rfile_called def test_get_contents(self): @@ -314,7 +478,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase): a = SCons.Action.CommandGeneratorAction(f) c = a.get_contents(target=[], source=[], - env=Environment({'foo':'FFF', 'bar' : 'BBB'})) + env=Environment(foo = 'FFF', bar = 'BBB')) assert c == "guux FFF BBB", c @@ -337,21 +501,65 @@ class FunctionActionTestCase(unittest.TestCase): s.inc = s.inc + 1 s.target = target s.source=source - assert env.subst("foo$BAR") == 'foofoo bar', env.subst("foo$BAR") - assert env.subst_list("foo$BAR") == [ [ 'foofoo', 'bar' ] ], \ - env.subst_list("foo$BAR") + assert env.subst("$BAR") == 'foo bar', env.subst("$BAR") return 0 a = SCons.Action.FunctionAction(f) - a.execute(target=1, source=2, env=Environment({'BAR':'foo bar','s':self})) + a.execute(target=1, source=2, env=Environment(BAR = 'foo bar', + s = self)) assert self.inc == 1, self.inc assert self.source == [2], self.source assert self.target == [1], self.target + global count + count = 0 + def function1(target, source, env): + global count + count = count + 1 + for t in target: + open(t, 'w').write("function1\n") + return 1 + + act = SCons.Action.FunctionAction(function1) + r = None + try: + r = act.execute(target = [outfile, outfile2], + source=[], + env=Environment()) + except SCons.Errors.BuildError: + pass + assert r == 1 + assert count == 1 + c = test.read(outfile, 'r') + assert c == "function1\n", c + c = test.read(outfile2, 'r') + assert c == "function1\n", c + + class class1a: + def __init__(self, target, source, env): + open(env['out'], 'w').write("class1a\n") + + act = SCons.Action.FunctionAction(class1a) + r = act.execute([], [], Environment(out = outfile)) + assert r.__class__ == class1a + c = test.read(outfile, 'r') + assert c == "class1a\n", c + + class class1b: + def __call__(self, target, source, env): + open(env['out'], 'w').write("class1b\n") + return 2 + + act = SCons.Action.FunctionAction(class1b()) + r = act.execute([], [], Environment(out = outfile)) + assert r == 2 + c = test.read(outfile, 'r') + assert c == "class1b\n", c + def test_get_contents(self): """Test fetching the contents of a function Action """ a = SCons.Action.FunctionAction(Func) - c = a.get_contents(target=[], source=[], env=Environment({})) + c = a.get_contents(target=[], source=[], env=Environment()) assert c == "\177\036\000\177\037\000d\000\000S", repr(c) class ListActionTestCase(unittest.TestCase): @@ -388,9 +596,29 @@ class ListActionTestCase(unittest.TestCase): s = env['s'] s.inc = s.inc + 1 a = SCons.Action.ListAction([f, f, f]) - a.execute([],[],Environment({'s':self})) + a.execute([], [], Environment(s = self)) assert self.inc == 3, self.inc + cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile) + + def function2(target, source, env): + open(env['out'], 'a').write("function2\n") + return 0 + + class class2a: + def __call__(self, target, source, env): + open(env['out'], 'a').write("class2a\n") + return 0 + + class class2b: + def __init__(self, target, source, env): + open(env['out'], 'a').write("class2b\n") + act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]) + r = act.execute([], [], Environment(out = outfile)) + assert r.__class__ == class2b + c = test.read(outfile, 'r') + assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c + def test_get_contents(self): """Test fetching the contents of a list of subsidiary Actions """ @@ -402,7 +630,7 @@ class ListActionTestCase(unittest.TestCase): a = SCons.Action.ListAction(["x", SCons.Action.CommandGenerator(gen), "z"]) - c = a.get_contents(target=[], source=[], env=Environment({'s':self})) + c = a.get_contents(target=[], source=[], env=Environment(s = self)) assert self.foo==1, self.foo assert c == "xyz", c @@ -429,7 +657,7 @@ class LazyActionTestCase(unittest.TestCase): s.test=1 return 0 a = SCons.Action.Action('$BAR') - a.execute([],[], env=Environment({'BAR':f,'s':self})) + a.execute([], [], env=Environment(BAR = f, s = self)) assert self.test == 1, self.test def test_get_contents(self): @@ -437,7 +665,7 @@ class LazyActionTestCase(unittest.TestCase): """ a = SCons.Action.Action("${FOO}") c = a.get_contents(target=[], source=[], - env=Environment({'FOO':[["This", "is", "$(", "a", "$)", "test"]]})) + env = Environment(FOO = [["This", "is", "$(", "a", "$)", "test"]])) assert c == "This is test", c diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 08d8f0cf..2f5df7f1 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -320,11 +320,6 @@ class BuilderBase: def get_actions(self): return self.action.get_actions() - def execute(self, target, source, env): - """Execute a builder's action to create an output object. - """ - return self.action.execute(target, source, env) - def get_raw_contents(self, target, source, env): """Fetch the "contents" of the builder's action. """ @@ -381,20 +376,6 @@ class ListBuilder(SCons.Util.Proxy): self.multi = builder.multi self.name = "ListBuilder(%s)"%builder.name - def execute(self, target, source, env): - if hasattr(self, 'status'): - return self.status - for t in self.tlist: - # unlink all targets and make all directories - # before building anything - t.prepare() - target = self.tlist - self.status = self.builder.execute(target, source, env) - for t in self.tlist: - if not t is target: - t.build() - return self.status - def targets(self, node): """Return the list of targets for this builder instance. """ diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 3c81d384..a2369b9f 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -51,25 +51,11 @@ import SCons.Environment # for each test, they can just use the one. test = TestCmd.TestCmd(workdir = '') -test.write('act.py', """import os, string, sys -f = open(sys.argv[1], 'w') -f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n") -try: - if sys.argv[3]: - f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n") -except: - pass -f.close() -sys.exit(0) -""") - -act_py = test.workpath('act.py') outfile = test.workpath('outfile') outfile2 = test.workpath('outfile2') show_string = None env_scanner = None -count = 0 scons_env = SCons.Environment.Environment() @@ -199,6 +185,11 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(name="builder", action="foo") assert builder.action.cmd_list == ["foo"] + def func(): + pass + builder = SCons.Builder.Builder(name="builder", action=func) + assert builder.action.function == func + def test_generator(self): """Test Builder creation given a generator function.""" @@ -218,204 +209,19 @@ class BuilderTestCase(unittest.TestCase): assert b1 != b3 assert b2 != b3 - def test_execute(self): - """Test execution of simple Builder objects - - One Builder is a string that executes an external command, - one is an internal Python function, one is a list - containing one of each. - """ - - def MyBuilder(**kw): - builder = apply(SCons.Builder.Builder, (), kw) - def no_show(str): - pass - builder.action.show = no_show - return builder - - python = sys.executable - - cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd1, name = "cmd1") - r = builder.execute([],[],Environment()) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: 'xyzzy'\n", c - - cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd2, name = "cmd2") - r = builder.execute('foo', [], Environment()) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: 'foo'\n", c - - cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd3, name = "cmd3") - r = builder.execute(['aaa', 'bbb'], [], Environment()) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: 'aaa' 'bbb'\n", c - - cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd4, name = "cmd4") - r = builder.execute([], ['one', 'two'], Environment()) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: 'one' 'two'\n", c - - cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd4, name = "cmd4") - r = builder.execute([], source = ['three', 'four', 'five'], env=Environment()) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: 'three' 'four'\n", c - - cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd5, name = "cmd5") - r = builder.execute(target = 'out5', source = [], env = Environment(ENV={'XYZZY' : 'xyzzy'})) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c - - class Obj: - def __init__(self, str): - self._str = str - def __str__(self): - return self._str - - cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile) - - builder = MyBuilder(action = cmd6, name = "cmd6") - r = builder.execute(target = [Obj('111'), Obj('222')], - source = [Obj('333'), Obj('444'), Obj('555')], - env = Environment()) - assert r == 0 - c = test.read(outfile, 'r') - assert c == "act.py: '222' '111' '333' '444'\n", c - - cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile, - python, act_py, outfile) - expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile, - python, act_py, outfile) - - builder = MyBuilder(action = cmd7, name = "cmd7") - - global show_string - show_string = "" - def my_show(string): - global show_string - show_string = show_string + string + "\n" - for action in builder.action.list: - action.show = my_show - - r = builder.execute([],[],Environment()) - assert r == 0 - assert show_string == expect7, show_string - - global count - count = 0 - def function1(target, source, env): - global count - count = count + 1 - for t in target: - open(t, 'w').write("function1\n") - return 1 - - builder = MyBuilder(action = function1, name = "function1") - try: - r = builder.execute(target = [outfile, outfile2], source=[], env=Environment()) - except SCons.Errors.BuildError: - pass - assert r == 1 - assert count == 1 - c = test.read(outfile, 'r') - assert c == "function1\n", c - c = test.read(outfile2, 'r') - assert c == "function1\n", c - - class class1a: - def __init__(self, target, source, env): - open(env['out'], 'w').write("class1a\n") - - builder = MyBuilder(action = class1a, name = "class1a") - r = builder.execute([],[],Environment(out = outfile)) - assert r.__class__ == class1a - c = test.read(outfile, 'r') - assert c == "class1a\n", c - - class class1b: - def __call__(self, target, source, env): - open(env['out'], 'w').write("class1b\n") - return 2 - - builder = MyBuilder(action = class1b(), name = "class1b") - r = builder.execute([],[],Environment(out = outfile)) - assert r == 2 - c = test.read(outfile, 'r') - assert c == "class1b\n", c - - cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile) - - def function2(target, source, env): - open(env['out'], 'a').write("function2\n") - return 0 - - class class2a: - def __call__(self, target, source, env): - open(env['out'], 'a').write("class2a\n") - return 0 - - class class2b: - def __init__(self, target, source, env): - open(env['out'], 'a').write("class2b\n") - - builder = MyBuilder(action = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]), name = "clist") - r = builder.execute([],[],Environment(out = outfile)) - assert r.__class__ == class2b - c = test.read(outfile, 'r') - assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c - - if os.name == 'nt': - # NT treats execs of directories and non-executable files - # as "file not found" errors - expect_nonexistent = 1 - expect_nonexecutable = 1 - else: - expect_nonexistent = 127 - expect_nonexecutable = 126 - - # Test that a nonexistent command returns 127 - builder = MyBuilder(action = python + "_XyZzY_", name="badcmd") - r = builder.execute([],[],Environment(out = outfile)) - assert r == expect_nonexistent, "r == %d" % r - - # Test that trying to execute a directory returns 126 - dir, tail = os.path.split(python) - builder = MyBuilder(action = dir, name = "dir") - r = builder.execute([],[],Environment(out = outfile)) - assert r == expect_nonexecutable, "r == %d" % r - - # Test that trying to execute a non-executable file returns 126 - builder = MyBuilder(action = outfile, name = "badfile") - r = builder.execute([],[],Environment(out = outfile)) - assert r == expect_nonexecutable, "r == %d" % r - def test_get_actions(self): """Test fetching the Builder's Action list - - Verify that we call the underlying Action's method """ - builder = SCons.Builder.Builder(name="builder", action=SCons.Action.ListAction(["x", "y", "z"])) + def func(): + pass + builder = SCons.Builder.Builder(name="builder", + action=SCons.Action.ListAction(["x", + func, + "z"])) a = builder.get_actions() assert len(a) == 3, a assert isinstance(a[0], SCons.Action.CommandAction), a[0] - assert isinstance(a[1], SCons.Action.CommandAction), a[1] + assert isinstance(a[1], SCons.Action.FunctionAction), a[1] assert isinstance(a[2], SCons.Action.CommandAction), a[2] def test_get_contents(self): @@ -542,11 +348,7 @@ class BuilderTestCase(unittest.TestCase): def test_ListBuilder(self): """Testing ListBuilder class.""" - global count - count = 0 def function2(target, source, env, tlist = [outfile, outfile2], **kw): - global count - count = count + 1 for t in target: open(str(t), 'w').write("function2\n") for t in tlist: @@ -556,25 +358,21 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(action = function2, name = "function2") tgts = builder(env, target = [outfile, outfile2], source = 'foo') + for t in tgts: + t.prepare() try: - r = tgts[0].builder.execute(tgts, 'foo', env) + tgts[0].build() except SCons.Errors.BuildError: pass c = test.read(outfile, 'r') assert c == "function2\n", c c = test.read(outfile2, 'r') assert c == "function2\n", c - r = tgts[1].builder.execute(tgts[1], 'foo', env) - assert r == 1, r - assert count == 1, count sub1_out = test.workpath('sub1', 'out') sub2_out = test.workpath('sub2', 'out') - count = 0 def function3(target, source, env, tlist = [sub1_out, sub2_out]): - global count - count = count + 1 for t in target: open(str(t), 'w').write("function3\n") for t in tlist: @@ -584,11 +382,12 @@ class BuilderTestCase(unittest.TestCase): builder = SCons.Builder.Builder(action = function3, name = "function3") tgts = builder(env, target = [sub1_out, sub2_out], source = 'foo') + for t in tgts: + t.prepare() try: - r = tgts[0].builder.execute(tgts, 'foo', env) - except: + tgts[0].build() + except SCons.Errors.BuildError: pass - assert r == 1, r c = test.read(sub1_out, 'r') assert c == "function3\n", c c = test.read(sub2_out, 'r') @@ -705,7 +504,6 @@ class BuilderTestCase(unittest.TestCase): flag = 1 assert flag, "UserError should be thrown when we build targets with files of different suffixes." - def test_build_scanner(self): """Testing ability to set a target scanner through a builder.""" global instanced @@ -743,7 +541,7 @@ class BuilderTestCase(unittest.TestCase): assert src.source_scanner == env_scanner def test_Builder_Args(self): - """Testing passing extra agrs to a builder.""" + """Testing passing extra args to a builder.""" def buildFunc(target, source, env, s=self): s.foo=env['foo'] s.bar=env['bar'] diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 59170440..b5c96f94 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -40,10 +40,16 @@ class Builder: def __init__(self, factory): self.factory = factory - def execute(self, target, source, env): - global built_it - built_it = 1 - return 0 + def get_actions(self): + class Action: + def execute(self, targets, sources, env): + global built_it + built_it = 1 + return 0 + return [Action()] + + def targets(self, t): + return [t] def source_factory(self, name): return self.factory(name) @@ -554,6 +560,8 @@ class FSTestCase(unittest.TestCase): fs.chdir(fs.Dir('..')) # Test scanning + f1.builder_set(Builder(fs.File)) + f1.env_set(Environment()) f1.target_scanner = Scanner() f1.scan() assert f1.implicit[0].path_ == os.path.join("d1", "f1") diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 4054bb9f..abfbe1c5 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -38,7 +38,7 @@ built_target = None built_source = None cycle_detected = None -class Builder: +class MyAction: def execute(self, target, source, env): global built_it, built_target, built_source, built_args built_it = 1 @@ -46,8 +46,12 @@ class Builder: built_source = source built_args = env return 0 + +class Builder: + def targets(self, t): + return [t] def get_actions(self): - return 'xyzzy' + return [MyAction()] def get_contents(self, target, source, env): return 7 @@ -89,58 +93,14 @@ 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" - - node = SCons.Node.Node() - node.builder_set(ExceptBuilder()) - node.env_set(Environment()) - try: - node.build() - except SCons.Errors.BuildError: - pass - else: - raise TestFailed, "did not catch expected BuildError" - - node = SCons.Node.Node() - node.builder_set(ExceptBuilder2()) - node.env_set(Environment()) - try: - node.build() - except SCons.Errors.BuildError, e: - # On a generic (non-BuildError) exception from a Builder, - # the Node should throw a BuildError exception with - # the args set to the exception value, type, and traceback. - assert len(e.args) == 3, `e.args` - assert e.args[0] == 'foo', e.args[0] - assert e.args[1] is None - assert type(e.args[2]) is type(sys.exc_traceback), e.args[2] - else: - raise TestFailed, "did not catch expected BuildError" - def test_build(self): """Test building a node """ global built_it class MyNode(SCons.Node.Node): - def __init__(self, **kw): - apply(SCons.Node.Node.__init__, (self,), kw) - self.prepare_count = 0 def __str__(self): return self.path - def prepare(self): - self.prepare_count = self.prepare_count+ 1 # Make sure it doesn't blow up if no builder is set. node = MyNode() node.build() @@ -153,8 +113,8 @@ class NodeTestCase(unittest.TestCase): node.sources = ["yyy", "zzz"] node.build() assert built_it - assert type(built_target) == type(MyNode()), type(built_target) - assert str(built_target) == "xxx", str(built_target) + assert type(built_target[0]) == type(MyNode()), type(built_target[0]) + assert str(built_target[0]) == "xxx", str(built_target[0]) assert built_source == ["yyy", "zzz"], built_source built_it = None @@ -166,8 +126,8 @@ class NodeTestCase(unittest.TestCase): node.overrides = { "foo" : 1, "bar" : 2 } node.build() assert built_it - assert type(built_target) == type(MyNode()), type(built_target) - assert str(built_target) == "qqq", str(built_target) + assert type(built_target[0]) == type(MyNode()), type(built_target[0]) + assert str(built_target[0]) == "qqq", str(built_target[0]) assert built_source == ["rrr", "sss"], built_source assert built_args["foo"] == 1, built_args assert built_args["bar"] == 2, built_args @@ -185,28 +145,6 @@ class NodeTestCase(unittest.TestCase): fff.sources = ["hhh", "iii"] ggg.sources = ["hhh", "iii"] - built_it = None - fff.build() - assert built_it - ggg.build() - assert ggg.prepare_count== 1, ggg.prepare_count - assert type(built_target) == type(MyNode()), type(built_target) - assert str(built_target) == "fff", str(built_target) - assert built_source == ["hhh", "iii"], built_source - - delattr(lb, 'status') - fff.prepare_count = 0 - ggg.prepare_count = 0 - - built_it = None - ggg.build() - #assert built_it - fff.build() - assert fff.prepare_count== 1, fff.prepare_count - assert type(built_target) == type(MyNode()), type(built_target) - assert str(built_target) == "fff", str(built_target) - assert built_source == ["hhh", "iii"], built_source - def test_depends_on(self): parent = SCons.Node.Node() child = SCons.Node.Node() @@ -249,8 +187,8 @@ class NodeTestCase(unittest.TestCase): """ node = SCons.Node.Node() node.builder_set(Builder()) - a = node.get_actions() - assert a == 'xyzzy', a + a = node.builder.get_actions() + assert isinstance(a[0], MyAction), a[0] def test_set_bsig(self): """Test setting a Node's signature diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 82dfd7e4..ca6e8eb8 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -46,13 +46,10 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" -import string -import types import copy -import sys -import SCons.Sig +import types -from SCons.Errors import BuildError, UserError +import SCons.Sig import SCons.Util # Node states @@ -110,38 +107,25 @@ class Node: def generate_build_env(self): return self.env.Override(self.overrides) - def get_actions(self): - """Fetch the action list to build.""" - return self.builder.get_actions() - def build(self): - """Actually build the node. Return the status from the build.""" - # This method is called from multiple threads in a parallel build, - # so only do thread safe stuff here. Do thread unsafe stuff in built(). + """Actually build the node. + + This method is called from multiple threads in a parallel build, + so only do thread safe stuff here. Do thread unsafe stuff in + built(). + """ if not self.builder: return None - try: - # If this Builder instance has already been called, - # there will already be an associated status. - stat = self.builder.status - except AttributeError: - try: - stat = self.builder.execute(self, self.sources, self.generate_build_env()) - except KeyboardInterrupt: - raise - except UserError: - raise - except BuildError: - raise - except: - raise BuildError(self, "Exception", - sys.exc_type, - sys.exc_value, - sys.exc_traceback) - if stat: - raise BuildError(node = self, errstr = "Error %d" % stat) - - return stat + action_list = self.builder.get_actions() + if not action_list: + return + targets = self.builder.targets(self) + env = self.generate_build_env() + for action in action_list: + stat = action.execute(targets, self.sources, env) + if stat: + raise SCons.Errors.BuildError(node = self, + errstr = "Error %d" % stat) def built(self): """Called just after this node is sucessfully built.""" diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index add52e48..0068e8c8 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -78,29 +78,9 @@ class BuildTask(SCons.Taskmaster.Task): if self.top and target.builder: display('scons: "%s" is up to date.' % str(target)) elif target.builder and not hasattr(target.builder, 'status'): - action_list = target.get_actions() - if not action_list: - return - env = target.generate_build_env() if print_time: start_time = time.time() - try: - for action in action_list: - stat = action.execute(self.targets, target.sources, env) - if stat: - raise BuildError(node = target, - errstr = "Error %d" % stat) - except KeyboardInterrupt: - raise - except UserError: - raise - except BuildError: - raise - except: - raise BuildError(target, "Exception", - sys.exc_type, - sys.exc_value, - sys.exc_traceback) + SCons.Taskmaster.Task.execute(self) if print_time: finish_time = time.time() global command_time @@ -131,7 +111,7 @@ class BuildTask(SCons.Taskmaster.Task): SCons.Taskmaster.Task.executed(self) else: SCons.Taskmaster.Task.executed(self) - + # print the tree here instead of in execute() because # this method is serialized, but execute isn't: if print_tree and self.top: @@ -142,14 +122,11 @@ class BuildTask(SCons.Taskmaster.Task): print SCons.Util.render_tree(self.targets[0], get_derived_children) def failed(self): - global exit_status - e = sys.exc_value if sys.exc_type == BuildError: sys.stderr.write("scons: *** [%s] %s\n" % (e.node, e.errstr)) if e.errstr == 'Exception': - traceback.print_exception(e.args[0], e.args[1], - e.args[2]) + traceback.print_exception(e.args[0], e.args[1], e.args[2]) elif sys.exc_type == UserError: # We aren't being called out of a user frame, so # don't try to walk the stack, just print the error. diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 043ce7da..eec50bf3 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -29,13 +29,12 @@ Generic Taskmaster. __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - - +import copy +import string +import sys import SCons.Node -import string import SCons.Errors -import copy class Task: """Default SCons build engine task. @@ -71,12 +70,24 @@ class Task: def execute(self): """Called to execute the task. - - This methods 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().""" - if self.targets[0].get_state() != SCons.Node.up_to_date: + + 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: self.targets[0].build() + except KeyboardInterrupt: + raise + except SCons.Errors.UserError: + raise + except SCons.Errors.BuildError: + raise + except: + raise SCons.Errors.BuildError(self.targets[0], + "Exception", + sys.exc_type, + sys.exc_value, + sys.exc_traceback) def get_target(self): """Fetch the target being built or updated by this task. diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py index c89f3560..d8bcc4fd 100644 --- a/src/engine/SCons/TaskmasterTests.py +++ b/src/engine/SCons/TaskmasterTests.py @@ -115,6 +115,10 @@ class Node: return self.name +class OtherError(Exception): + pass + + class TaskmasterTestCase(unittest.TestCase): def test_next_task(self): @@ -337,8 +341,10 @@ class TaskmasterTestCase(unittest.TestCase): assert not tm.next_task() t.executed() - def test_cycle_detection(self): + """Test detecting dependency cycles + + """ n1 = Node("n1") n2 = Node("n2", [n1]) n3 = Node("n3", [n2]) @@ -409,6 +415,8 @@ class TaskmasterTestCase(unittest.TestCase): assert tm.next_task() is None def test_executed(self): + """Test when a task has been executed + """ pass def test_prepare(self): @@ -430,6 +438,66 @@ class TaskmasterTestCase(unittest.TestCase): assert n1.prepared assert n2.prepared + def test_execute(self): + """Test executing a task + + """ + global built_text + + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + t = tm.next_task() + t.execute() + assert built_text == "n1 built", built_text + + def raise_UserError(): + raise SCons.Errors.UserError + n2 = Node("n2") + n2.build = raise_UserError + tm = SCons.Taskmaster.Taskmaster([n2]) + t = tm.next_task() + try: + t.execute() + except SCons.Errors.UserError: + pass + else: + raise TestFailed, "did not catch expected UserError" + + def raise_BuildError(): + raise SCons.Errors.BuildError + n3 = Node("n3") + n3.build = raise_BuildError + tm = SCons.Taskmaster.Taskmaster([n3]) + t = tm.next_task() + try: + t.execute() + except SCons.Errors.BuildError: + pass + else: + raise TestFailed, "did not catch expected BuildError" + + def raise_OtherError(): + raise OtherError + n4 = Node("n4") + n4.build = raise_OtherError + tm = SCons.Taskmaster.Taskmaster([n4]) + t = tm.next_task() + try: + t.execute() + except SCons.Errors.BuildError, e: + # On a generic (non-BuildError) exception from a Builder, + # the target should throw a BuildError exception with the + # args set to the exception value, instance, and traceback. + assert e.node == n4, e.node + assert e.errstr == "Exception", e.errstr + assert len(e.args) == 3, `e.args` + assert e.args[0] == OtherError, e.args[0] + assert isinstance(e.args[1], OtherError), type(e.args[1]) + assert type(e.args[2]) == type(sys.exc_traceback), e.args[2] + else: + raise TestFailed, "did not catch expected BuildError" + + if __name__ == "__main__":