From 2af9b1c17b019d339ad31db5ca310023b1448c40 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 27 Sep 2004 21:23:04 +0000 Subject: Add support for changing directory when executing Actions (the to the target directory by default). --- doc/man/scons.1 | 158 +++++++++++++++++++++- src/CHANGES.txt | 4 + src/engine/SCons/Action.py | 76 ++++++----- src/engine/SCons/ActionTests.py | 64 +++++---- src/engine/SCons/Builder.py | 24 +++- src/engine/SCons/Executor.py | 16 +-- src/engine/SCons/ExecutorTests.py | 15 ++- src/engine/SCons/Node/FS.py | 4 - src/engine/SCons/Node/FSTests.py | 16 --- test/SConscriptChdir.py | 85 ++++++++++++ test/chdir.py | 276 ++++++++++++++++++++++++++++++++++++++ test/strfunction.py | 3 - 12 files changed, 638 insertions(+), 103 deletions(-) create mode 100644 test/SConscriptChdir.py create mode 100644 test/chdir.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 5601f98..5d44c32 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -1281,6 +1281,64 @@ we have to access the first element in the list to get at the Node that actually represents the object file. +Builder calls support a +.B chdir +keyword argument that +specifies that the Builder's action(s) +should be executed +after changing directory. +If the +.B chdir +argument is +a string or a directory Node, +scons will change to the specified directory. +If the +.B chdir +is not a string or Node +and is non-zero, +then scons will change to the +target file's directory. + +.ES +# scons will change to the "sub" subdirectory +# before executing the "cp" command. +env.Command('sub/dir/foo.out', 'sub/dir/foo.in', + "cp dir/foo.in dir/foo.out", + chdir='sub') + +# Because chdir is not a string, scons will change to the +# target's directory ("sub/dir") before executing the +# "cp" command. +env.Command('sub/dir/foo.out', 'sub/dir/foo.in', + "cp foo.in foo.out", + chdir=1) +.EE + +Note that scons will +.I not +automatically modify +its expansion of +construction variables like +.B $TARGET +and +.B $SOURCE +when using the chdir +keyword argument--that is, +the expanded file names +will still be relative to +the top-level SConstruct directory, +and consequently incorrect +relative to the chdir directory. +If you use the chdir keyword argument, +you will typically need to supply a different +command line using +expansions like +.B ${TARGET.file} +and +.B ${SOURCE.file} +to use just the filename portion of the +targets and source. + .B scons provides the following builder methods: @@ -7724,6 +7782,54 @@ env = Environment(BUILDERS = {'MyBuild' : b}) env.MyBuild('foo.out', 'foo.in', my_arg = 'xyzzy') .EE +.IP chdir +A directory from which scons +will execute the +action(s) specified +for this Builder. +If the +.B chdir +argument is +a string or a directory Node, +scons will change to the specified directory. +If the +.B chdir +is not a string or Node +and is non-zero, +then scons will change to the +target file's directory. + +Note that scons will +.I not +automatically modify +its expansion of +construction variables like +.B $TARGET +and +.B $SOURCE +when using the chdir +keyword argument--that is, +the expanded file names +will still be relative to +the top-level SConstruct directory, +and consequently incorrect +relative to the chdir directory. +Builders created using chdir keyword argument, +will need to use construction variable +expansions like +.B ${TARGET.file} +and +.B ${SOURCE.file} +to use just the filename portion of the +targets and source. + +.ES +b = Builder(action="build < ${SOURCE.file} > ${TARGET.file}", + chdir=1) +env = Environment(BUILDERS = {'MyBuild' : b}) +env.MyBuild('sub/dir/foo.out', 'sub/dir/foo.in') +.EE + .RE Any additional keyword arguments supplied when a Builder object is created @@ -7863,6 +7969,9 @@ def build_it(target = None, source = None, env = None): a = Action(build_it) .EE +If the action argument is not one of the above, +None is returned. + The second, optional argument is a Python function that returns a string to be printed to describe the action being executed. @@ -7926,8 +8035,53 @@ a = Action(build_it, string_it, ['XXX']) a = Action(build_it, varlist=['XXX']) .EE .PP -If the action argument is not one of the above, -None is returned. + +The +.BR Action () +global function +also takes a +.B chdir +keyword argument +which specifies that +scons will execute the action +after changing to the specified directory. +If the chdir argument is +a string or a directory Node, +scons will change to the specified directory. +If the chdir argument +is not a string or Node +and is non-zero, +then scons will change to the +target file's directory. + +Note that scons will +.I not +automatically modify +its expansion of +construction variables like +.B $TARGET +and +.B $SOURCE +when using the chdir +keyword argument--that is, +the expanded file names +will still be relative to +the top-level SConstruct directory, +and consequently incorrect +relative to the chdir directory. +Builders created using chdir keyword argument, +will need to use construction variable +expansions like +.B ${TARGET.file} +and +.B ${SOURCE.file} +to use just the filename portion of the +targets and source. + +.ES +a = Action("build < ${SOURCE.file} > ${TARGET.file}", + chdir=1) +.EE .SS Miscellaneous Action Functions diff --git a/src/CHANGES.txt b/src/CHANGES.txt index f1239de..4f50aec 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -72,6 +72,10 @@ RELEASE 0.97 - XXX - Add a ParseDepends() function that will parse up a list of explicit dependencies from a "make depend" style file. + - Support the ability to change directory when executing an Action + through "chdir" keyword arguments to Action and Builder creation + and calls. + From Clive Levinson: - Make ParseConfig() recognize and add -mno-cygwin to $LINKFLAGS and diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 927e3d5..ba1c240 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -191,29 +191,29 @@ def _do_create_action(act, *args, **kw): return apply(ListAction, (listCmdActions,)+args, kw) return None -def Action(act, strfunction=_null, varlist=[], presub=_null): +def Action(act, *args, **kw): """A factory for action objects.""" if SCons.Util.is_List(act): - acts = map(lambda x, s=strfunction, v=varlist, ps=presub: - _do_create_action(x, strfunction=s, varlist=v, presub=ps), + acts = map(lambda a, args=args, kw=kw: + apply(_do_create_action, (a,)+args, kw), act) - acts = filter(lambda x: not x is None, acts) + acts = filter(None, acts) if len(acts) == 1: return acts[0] else: - return ListAction(acts, strfunction=strfunction, varlist=varlist, presub=presub) + return apply(ListAction, (acts,)+args, kw) else: - return _do_create_action(act, strfunction=strfunction, varlist=varlist, presub=presub) + return apply(_do_create_action, (act,)+args, kw) class ActionBase: """Base class for actions that create output objects.""" - def __init__(self, strfunction=_null, presub=_null, **kw): + def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw): if not strfunction is _null: self.strfunction = strfunction if presub is _null: - self.presub = print_actions_presub - else: - self.presub = presub + presub = print_actions_presub + self.presub = presub + self.chdir = chdir def __cmp__(self, other): return cmp(self.__dict__, other) @@ -225,7 +225,8 @@ class ActionBase: errfunc=None, presub=_null, show=_null, - execute=_null): + execute=_null, + chdir=_null): if not SCons.Util.is_List(target): target = [target] if not SCons.Util.is_List(source): @@ -233,14 +234,26 @@ class ActionBase: if presub is _null: presub = self.presub if show is _null: show = print_actions if execute is _null: execute = execute_actions + if chdir is _null: chdir = self.chdir + save_cwd = None + if chdir: + save_cwd = os.getcwd() + try: + chdir = str(chdir.abspath) + except AttributeError: + if not SCons.Util.is_String(chdir): + chdir = str(target[0].dir) if presub: t = string.join(map(str, target), 'and') l = string.join(self.presub_lines(env), '\n ') out = "Building %s with action(s):\n %s\n" % (t, l) sys.stdout.write(out) + s = None if show and self.strfunction: s = self.strfunction(target, source, env) if s: + if chdir: + s = ('os.chdir(%s)\n' % repr(chdir)) + s try: get = env.get except AttributeError: @@ -250,13 +263,20 @@ class ActionBase: if not print_func: print_func = self.print_cmd_line print_func(s, target, source, env) + stat = 0 if execute: - stat = self.execute(target, source, env) - if stat and errfunc: - errfunc(stat) - return stat - else: - return 0 + if chdir: + os.chdir(chdir) + try: + stat = self.execute(target, source, env) + if stat and errfunc: + errfunc(stat) + finally: + if save_cwd: + os.chdir(save_cwd) + if s and save_cwd: + print_func('os.chdir(%s)' % repr(save_cwd), target, source, env) + return stat def presub_lines(self, env): # CommandGeneratorAction needs a real environment @@ -273,9 +293,6 @@ class ActionBase: def genstring(self, target, source, env): return str(self) - def get_actions(self): - return [self] - def __add__(self, other): return _actionAppend(self, other) @@ -294,12 +311,12 @@ def _string_from_cmd_list(cmd_list): class CommandAction(ActionBase): """Class for command-execution actions.""" - def __init__(self, cmd, **kw): + def __init__(self, cmd, *args, **kw): # Cmd list can actually be a list or a single item...basically # anything that we could pass in as the first arg to # Environment.subst_list(). if __debug__: logInstanceCreation(self) - apply(ActionBase.__init__, (self,), kw) + apply(ActionBase.__init__, (self,)+args, kw) self.cmd_list = cmd def __str__(self): @@ -383,9 +400,9 @@ class CommandAction(ActionBase): class CommandGeneratorAction(ActionBase): """Class for command-generator actions.""" - def __init__(self, generator, **kw): + def __init__(self, generator, *args, **kw): if __debug__: logInstanceCreation(self) - apply(ActionBase.__init__, (self,), kw) + apply(ActionBase.__init__, (self,)+args, kw) self.generator = generator def __generate(self, target, source, env, for_signature): @@ -467,10 +484,10 @@ class LazyCmdGenerator: class FunctionAction(ActionBase): """Class for Python function actions.""" - def __init__(self, execfunction, **kw): + def __init__(self, execfunction, *args, **kw): if __debug__: logInstanceCreation(self) self.execfunction = execfunction - apply(ActionBase.__init__, (self,), kw) + apply(ActionBase.__init__, (self,)+args, kw) self.varlist = kw.get('varlist', []) def function_name(self): @@ -540,14 +557,11 @@ class FunctionAction(ActionBase): class ListAction(ActionBase): """Class for lists of other actions.""" - def __init__(self, list, **kw): + def __init__(self, list, *args, **kw): if __debug__: logInstanceCreation(self) - apply(ActionBase.__init__, (self,), kw) + apply(ActionBase.__init__, (self,)+args, kw) self.list = map(lambda x: Action(x), list) - def get_actions(self): - return self.list - def __str__(self): s = [] for l in self.list: diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index bc2387a..af03cd5 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -324,7 +324,10 @@ class ActionBaseTestCase(unittest.TestCase): """Test creation of ActionBase objects """ - def func(): + def func1(): + pass + + def func2(): pass a = SCons.Action.ActionBase() @@ -334,11 +337,19 @@ class ActionBaseTestCase(unittest.TestCase): assert not hasattr(a, 'strfunction') assert not hasattr(a, 'kwarg') - a = SCons.Action.ActionBase(func) - assert a.strfunction is func, a.strfunction + a = SCons.Action.ActionBase(strfunction=func1) + assert a.strfunction is func1, a.strfunction + + a = SCons.Action.ActionBase(presub=func1) + assert a.presub is func1, a.presub + + a = SCons.Action.ActionBase(chdir=1) + assert a.chdir is 1, a.chdir - a = SCons.Action.ActionBase(strfunction=func) - assert a.strfunction is func, a.strfunction + a = SCons.Action.ActionBase(func1, func2, 'x') + assert a.strfunction is func1, a.strfunction + assert a.presub is func2, a.presub + assert a.chdir is 'x', a.chdir def test___cmp__(self): """Test Action comparison @@ -379,6 +390,10 @@ class ActionBaseTestCase(unittest.TestCase): save_execute_actions = SCons.Action.execute_actions #SCons.Action.print_actions = 0 + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub', 'xyz') + os.chdir(test.workpath()) + try: env = Environment() @@ -395,6 +410,25 @@ class ActionBaseTestCase(unittest.TestCase): s = sio.getvalue() assert s == 'execfunc(["out"], ["in"])\n', s + a.chdir = 'xyz' + expect = 'os.chdir(\'%s\')\nexecfunc(["out"], ["in"])\nos.chdir(\'%s\')\n' + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env) + assert result == 7, result + s = sio.getvalue() + assert s == expect % ('xyz', test.workpath()), s + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env, chdir='sub') + assert result == 7, result + s = sio.getvalue() + assert s == expect % ('sub', test.workpath()), s + + a.chdir = None + SCons.Action.execute_actions = 0 sio = StringIO.StringIO() @@ -503,13 +537,6 @@ class ActionBaseTestCase(unittest.TestCase): s = a.presub_lines(Environment(ACT = 'expanded action')) assert s == ['expanded action'], s - def test_get_actions(self): - """Test the get_actions() method - """ - a = SCons.Action.Action("x") - l = a.get_actions() - assert l == [a], l - def test_add(self): """Test adding Actions to stuff.""" # Adding actions to other Actions or to stuff that can @@ -1336,19 +1363,6 @@ class ListActionTestCase(unittest.TestCase): assert isinstance(a.list[2], SCons.Action.ListAction) assert a.list[2].list[0].cmd_list == 'y' - def test_get_actions(self): - """Test the get_actions() method for ListActions - """ - a = SCons.Action.ListAction(["x", "y"]) - l = a.get_actions() - assert len(l) == 2, l - assert isinstance(l[0], SCons.Action.CommandAction), l[0] - g = l[0].get_actions() - assert g == [l[0]], g - assert isinstance(l[1], SCons.Action.CommandAction), l[1] - g = l[1].get_actions() - assert g == [l[1]], g - def test___str__(self): """Test the __str__() method for a list of subsidiary Actions """ diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 7a96aed..8e46332 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -274,7 +274,7 @@ def Builder(**kw): return ret -def _init_nodes(builder, env, overrides, tlist, slist): +def _init_nodes(builder, env, overrides, executor_kw, tlist, slist): """Initialize lists of target and source nodes with all of the proper Builder information. """ @@ -335,7 +335,8 @@ def _init_nodes(builder, env, overrides, tlist, slist): env or builder.env, [builder.overrides, overrides], tlist, - slist) + slist, + executor_kw) # Now set up the relevant information in the target Nodes themselves. for t in tlist: @@ -410,6 +411,7 @@ class BuilderBase: env = None, single_source = 0, name = None, + chdir = _null, **overrides): if __debug__: logInstanceCreation(self, 'BuilderBase') self.action = SCons.Action.Action(action) @@ -443,6 +445,9 @@ class BuilderBase: # that don't get attached to construction environments. if name: self.name = name + self.executor_kw = {} + if not chdir is _null: + self.executor_kw['chdir'] = chdir def __nonzero__(self): raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead" @@ -547,7 +552,7 @@ class BuilderBase: return tlist, slist - def _execute(self, env, target = None, source = _null, overwarn={}): + def _execute(self, env, target=None, source=_null, overwarn={}, executor_kw={}): if source is _null: source = target target = None @@ -572,12 +577,17 @@ class BuilderBase: builder = self else: builder = ListBuilder(self, env, tlist) - _init_nodes(builder, env, overwarn.data, tlist, slist) + _init_nodes(builder, env, overwarn.data, executor_kw, tlist, slist) return tlist - def __call__(self, env, target = None, source = _null, **kw): - return self._execute(env, target, source, OverrideWarner(kw)) + def __call__(self, env, target=None, source=_null, chdir=_null, **kw): + if chdir is _null: + ekw = self.executor_kw + else: + ekw = self.executor_kw.copy() + ekw['chdir'] = chdir + return self._execute(env, target, source, OverrideWarner(kw), ekw) def adjust_suffix(self, suff): if suff and not suff[0] in [ '.', '_', '$' ]: @@ -692,7 +702,7 @@ class MultiStepBuilder(BuilderBase): self.sdict = {} self.cached_src_suffixes = {} # source suffixes keyed on id(env) - def _execute(self, env, target = None, source = _null, overwarn={}): + def _execute(self, env, target = None, source = _null, overwarn={}, executor_kw={}): if source is _null: source = target target = None diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py index b9d9897..47a72de 100644 --- a/src/engine/SCons/Executor.py +++ b/src/engine/SCons/Executor.py @@ -43,15 +43,17 @@ class Executor: and sources for later processing as needed. """ - def __init__(self, action, env=None, overridelist=[], targets=[], sources=[]): + def __init__(self, action, env=None, overridelist=[], + targets=[], sources=[], builder_kw={}): if __debug__: logInstanceCreation(self) + if not action: + raise SCons.Errors.UserError, "Executor must have an action." self.action = action self.env = env self.overridelist = overridelist self.targets = targets self.sources = sources[:] - if not action: - raise SCons.Errors.UserError, "Executor must have an action." + self.builder_kw = builder_kw def get_build_env(self): """Fetch or create the appropriate build Environment @@ -96,11 +98,7 @@ class Executor: involved, so only one target's pre- and post-actions will win, anyway. This is probably a bug we should fix... """ - try: - al = self.action_list - except AttributeError: - al = self.action.get_actions() - self.action_list = al + al = [self.action] try: # XXX shouldn't reach into node attributes like this return target.pre_actions + al + target.post_actions @@ -113,6 +111,8 @@ class Executor: if not action_list: return env = self.get_build_env() + kw = kw.copy() + kw.update(self.builder_kw) for action in action_list: apply(action, (self.targets, self.sources, env, errfunc), kw) diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py index cfa2dcd..e259012 100644 --- a/src/engine/SCons/ExecutorTests.py +++ b/src/engine/SCons/ExecutorTests.py @@ -46,8 +46,9 @@ class MyEnvironment: class MyAction: def __init__(self, actions=['action1', 'action2']): self.actions = actions - def get_actions(self): - return self.actions + def __call__(self, target, source, env, errfunc, **kw): + for action in self.actions: + action(target, source, env, errfunc) def strfunction(self, target, source, env): return string.join(['STRFUNCTION'] + self.actions + target + source) def genstring(self, target, source, env): @@ -120,15 +121,15 @@ class ExecutorTestCase(unittest.TestCase): def test_get_action_list(self): """Test fetching and generating an action list""" x = SCons.Executor.Executor('b', 'e', 'o', 't', 's') - x.action_list = ['aaa'] al = x.get_action_list(MyNode([], [])) - assert al == ['aaa'], al + assert al == ['b'], al al = x.get_action_list(MyNode(['PRE'], ['POST'])) - assert al == ['PRE', 'aaa', 'POST'], al + assert al == ['PRE', 'b', 'POST'], al - x = SCons.Executor.Executor(MyAction(), None, {}, 't', 's') + a = MyAction() + x = SCons.Executor.Executor(a, None, {}, 't', 's') al = x.get_action_list(MyNode(['pre'], ['post'])) - assert al == ['pre', 'action1', 'action2', 'post'], al + assert al == ['pre', a, 'post'], al def test__call__(self): """Test calling an Executor""" diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 5cdece1..2bd68e1 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1263,10 +1263,6 @@ class Dir(Base): kids.sort(c) self._add_child(self.implicit, self.implicit_dict, kids) - def get_actions(self): - """A null "builder" for directories.""" - return [] - def build(self, **kw): """A null "builder" for directories.""" global MkdirBuilder diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 4501cf1..ca14c3b 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -87,9 +87,6 @@ class Action: pass def strfunction(self, targets, sources, env): return "" - def get_actions(self): - return [self] - class Builder: def __init__(self, factory, action=Action()): self.factory = factory @@ -97,9 +94,6 @@ class Builder: self.overrides = {} self.action = action - def get_actions(self): - return [self] - def targets(self, t): return [t] @@ -1541,15 +1535,6 @@ class prepareTestCase(unittest.TestCase): dir = fs.Dir("dir") dir.prepare() -class get_actionsTestCase(unittest.TestCase): - def runTest(self): - """Test the Dir's get_action() method""" - - fs = SCons.Node.FS.FS() - dir = fs.Dir('.') - a = dir.get_actions() - assert a == [], a - class SConstruct_dirTestCase(unittest.TestCase): def runTest(self): """Test setting the SConstruct directory""" @@ -2013,7 +1998,6 @@ if __name__ == "__main__": suite.addTest(stored_infoTestCase()) suite.addTest(has_src_builderTestCase()) suite.addTest(prepareTestCase()) - suite.addTest(get_actionsTestCase()) suite.addTest(SConstruct_dirTestCase()) suite.addTest(CacheDirTestCase()) suite.addTest(clearTestCase()) diff --git a/test/SConscriptChdir.py b/test/SConscriptChdir.py new file mode 100644 index 0000000..3763378 --- /dev/null +++ b/test/SConscriptChdir.py @@ -0,0 +1,85 @@ +#!/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 + +test = TestSCons.TestSCons() + +test.subdir('dir1', 'dir2', 'dir3', 'dir4', 'dir5') + +test.write('SConstruct', """ +env = Environment() +SConscript('dir1/SConscript') +SConscriptChdir(1) +SConscript('dir2/SConscript') +SConscriptChdir(0) +SConscript('dir3/SConscript') +env.SConscriptChdir(1) +SConscript('dir4/SConscript') +env.SConscriptChdir(0) +SConscript('dir5/SConscript') +""") + +test.write(['dir1', 'SConscript'], """ +execfile("create_test.py") +""") + +test.write(['dir2', 'SConscript'], """ +execfile("create_test.py") +""") + +test.write(['dir3', 'SConscript'], """ +import os.path +name = os.path.join('dir3', 'create_test.py') +execfile(name) +""") + +test.write(['dir4', 'SConscript'], """ +execfile("create_test.py") +""") + +test.write(['dir5', 'SConscript'], """ +import os.path +name = os.path.join('dir5', 'create_test.py') +execfile(name) +""") + +for dir in ['dir1', 'dir2', 'dir3','dir4', 'dir5']: + test.write([dir, 'create_test.py'], r""" +f = open("test.txt", "ab") +f.write("This is the %s test.\n") +f.close() +""" % dir) + +test.run(arguments=".", stderr=None) + +test.fail_test(test.read(['dir1', 'test.txt']) != "This is the dir1 test.\n") +test.fail_test(test.read(['dir2', 'test.txt']) != "This is the dir2 test.\n") +test.fail_test(test.read('test.txt') != "This is the dir3 test.\nThis is the dir5 test.\n") +test.fail_test(test.read(['dir4', 'test.txt']) != "This is the dir4 test.\n") + +test.pass_test() diff --git a/test/chdir.py b/test/chdir.py new file mode 100644 index 0000000..e2d0894 --- /dev/null +++ b/test/chdir.py @@ -0,0 +1,276 @@ +#!/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__" + +""" +Test that the chdir argument to Builder creation, Action creation, +Command() calls and execution work1s correctly. +""" + +import TestSCons + +python = TestSCons.python + +test = TestSCons.TestSCons() + +test.subdir('other1', + 'other2', + 'other3', + 'other4', + 'other5', + 'other6', + 'other7', + 'other8', + 'other9', + 'work1', + ['work1', 'sub1'], + ['work1', 'sub2'], + ['work1', 'sub3'], + ['work1', 'sub4'], + ['work1', 'sub5'], + ['work1', 'sub6'], + ['work1', 'sub7'], + ['work1', 'sub8'], + ['work1', 'sub9'], + ['work1', 'sub20'], + ['work1', 'sub21'], + ['work1', 'sub22'], + ['work1', 'sub23']) + +cat_py = test.workpath('cat.py') + +other1 = test.workpath('other1') +other1_f11_out = test.workpath('other1', 'f11.out') +other1_f11_in = test.workpath('other1', 'f11.in') +other2 = test.workpath('other2') +other2_f12_out = test.workpath('other2', 'f12.out') +other2_f12_in = test.workpath('other2', 'f12.in') +other3 = test.workpath('other3') +other3_f13_out = test.workpath('other3', 'f13.out') +other3_f13_in = test.workpath('other3', 'f13.in') +other4 = test.workpath('other4') +other4_f14_out = test.workpath('other4', 'f14.out') +other4_f14_in = test.workpath('other4', 'f14.in') +other5 = test.workpath('other5') +other5_f15_out = test.workpath('other5', 'f15.out') +other5_f15_in = test.workpath('other5', 'f15.in') +other6 = test.workpath('other6') +other6_f16_out = test.workpath('other6', 'f16.out') +other6_f16_in = test.workpath('other6', 'f16.in') +other7 = test.workpath('other7') +other7_f17_out = test.workpath('other7', 'f17.out') +other7_f17_in = test.workpath('other7', 'f17.in') +other8 = test.workpath('other8') +other8_f18_out = test.workpath('other8', 'f18.out') +other8_f18_in = test.workpath('other8', 'f18.in') +other9 = test.workpath('other9') +other9_f19_out = test.workpath('other9', 'f19.out') +other9_f19_in = test.workpath('other9', 'f19.in') + +test.write(cat_py, """\ +import sys +ofp = open(sys.argv[1], 'w') +for ifp in map(open, sys.argv[2:]): + ofp.write(ifp.read()) +ofp.close +""") + +test.write(['work1', 'SConstruct'], """ +cat_command = r"%(python)s %(cat_py)s ${TARGET.file} ${SOURCE.file}" + +no_chdir_act = Action(cat_command) +chdir_sub4_act = Action(cat_command, chdir=1) +chdir_sub5_act = Action(cat_command, chdir='sub5') +chdir_sub6_act = Action(cat_command, chdir=Dir('sub6')) + +env = Environment(BUILDERS = { + 'Chdir4' : Builder(action = chdir_sub4_act), + 'Chdir5' : Builder(action = chdir_sub5_act), + 'Chdir6' : Builder(action = chdir_sub6_act), + 'Chdir7' : Builder(action = no_chdir_act, chdir=1), + 'Chdir8' : Builder(action = no_chdir_act, chdir='sub8'), + 'Chdir9' : Builder(action = no_chdir_act, chdir=Dir('sub9')), +}) + +env.Command('f0.out', 'f0.in', cat_command) + +env.Command('sub1/f1.out', 'sub1/f1.in', cat_command, + chdir=1) +env.Command('sub2/f2.out', 'sub2/f2.in', cat_command, + chdir='sub2') +env.Command('sub3/f3.out', 'sub3/f3.in', cat_command, + chdir=Dir('sub3')) + +env.Chdir4('sub4/f4.out', 'sub4/f4.in') +env.Chdir5('sub5/f5.out', 'sub5/f5.in') +env.Chdir6('sub6/f6.out', 'sub6/f6.in') + +env.Chdir7('sub7/f7.out', 'sub7/f7.in') +env.Chdir8('sub8/f8.out', 'sub8/f8.in') +env.Chdir9('sub9/f9.out', 'sub9/f9.in') + +env.Command(r'%(other1_f11_out)s', r'%(other1_f11_in)s', cat_command, + chdir=1) +env.Command(r'%(other2_f12_out)s', r'%(other2_f12_in)s', cat_command, + chdir=r'%(other2)s') +env.Command(r'%(other3_f13_out)s', r'%(other3_f13_in)s', cat_command, + chdir=Dir(r'%(other3)s')) + +env.Chdir4(r'%(other4_f14_out)s', r'%(other4_f14_in)s') +env.Chdir5(r'%(other5_f15_out)s', r'%(other5_f15_in)s', + chdir=r'%(other5)s') +env.Chdir6(r'%(other6_f16_out)s', r'%(other6_f16_in)s', + chdir=Dir(r'%(other6)s')) + +env.Chdir7(r'%(other7_f17_out)s', r'%(other7_f17_in)s') +env.Chdir8(r'%(other8_f18_out)s', r'%(other8_f18_in)s', + chdir=r'%(other8)s') +env.Chdir9(r'%(other9_f19_out)s', r'%(other9_f19_in)s', + chdir=Dir(r'%(other9)s')) + +Command('f20.out', 'f20.in', cat_command) + +Command('sub21/f21.out', 'sub21/f21.in', cat_command, + chdir=1) +Command('sub22/f22.out', 'sub22/f22.in', cat_command, + chdir='sub22') +Command('sub23/f23.out', 'sub23/f23.in', cat_command, + chdir=Dir('sub23')) +""" % locals()) + +test.write(['work1', 'f0.in'], "work1/f0.in\n") + +test.write(['work1', 'sub1', 'f1.in'], "work1/sub1/f1.in\n") +test.write(['work1', 'sub2', 'f2.in'], "work1/sub2/f2.in\n") +test.write(['work1', 'sub3', 'f3.in'], "work1/sub3/f3.in\n") +test.write(['work1', 'sub4', 'f4.in'], "work1/sub4/f4.in\n") +test.write(['work1', 'sub5', 'f5.in'], "work1/sub5/f5.in\n") +test.write(['work1', 'sub6', 'f6.in'], "work1/sub6/f6.in\n") +test.write(['work1', 'sub7', 'f7.in'], "work1/sub7/f7.in\n") +test.write(['work1', 'sub8', 'f8.in'], "work1/sub8/f8.in\n") +test.write(['work1', 'sub9', 'f9.in'], "work1/sub9/f9.in\n") + +test.write(['other1', 'f11.in'], "other1/f11.in\n") +test.write(['other2', 'f12.in'], "other2/f12.in\n") +test.write(['other3', 'f13.in'], "other3/f13.in\n") +test.write(['other4', 'f14.in'], "other4/f14.in\n") +test.write(['other5', 'f15.in'], "other5/f15.in\n") +test.write(['other6', 'f16.in'], "other6/f16.in\n") +test.write(['other7', 'f17.in'], "other7/f17.in\n") +test.write(['other8', 'f18.in'], "other8/f18.in\n") +test.write(['other9', 'f19.in'], "other9/f19.in\n") + +test.write(['work1', 'f20.in'], "work1/f20.in\n") + +test.write(['work1', 'sub21', 'f21.in'], "work1/sub21/f21.in\n") +test.write(['work1', 'sub22', 'f22.in'], "work1/sub22/f22.in\n") +test.write(['work1', 'sub23', 'f23.in'], "work1/sub23/f23.in\n") + +test.run(chdir='work1', arguments='..') + +test.must_match(['work1', 'f0.out'], "work1/f0.in\n") + +test.must_match(['work1', 'sub1', 'f1.out'], "work1/sub1/f1.in\n") +test.must_match(['work1', 'sub2', 'f2.out'], "work1/sub2/f2.in\n") +test.must_match(['work1', 'sub3', 'f3.out'], "work1/sub3/f3.in\n") +test.must_match(['work1', 'sub4', 'f4.out'], "work1/sub4/f4.in\n") +test.must_match(['work1', 'sub5', 'f5.out'], "work1/sub5/f5.in\n") +test.must_match(['work1', 'sub6', 'f6.out'], "work1/sub6/f6.in\n") +test.must_match(['work1', 'sub7', 'f7.out'], "work1/sub7/f7.in\n") +test.must_match(['work1', 'sub8', 'f8.out'], "work1/sub8/f8.in\n") +test.must_match(['work1', 'sub9', 'f9.out'], "work1/sub9/f9.in\n") + +test.must_match(['other1', 'f11.out'], "other1/f11.in\n") +test.must_match(['other2', 'f12.out'], "other2/f12.in\n") +test.must_match(['other3', 'f13.out'], "other3/f13.in\n") +test.must_match(['other4', 'f14.out'], "other4/f14.in\n") +test.must_match(['other5', 'f15.out'], "other5/f15.in\n") +test.must_match(['other6', 'f16.out'], "other6/f16.in\n") +test.must_match(['other7', 'f17.out'], "other7/f17.in\n") +test.must_match(['other8', 'f18.out'], "other8/f18.in\n") +test.must_match(['other9', 'f19.out'], "other9/f19.in\n") + +test.must_match(['work1', 'f20.out'], "work1/f20.in\n") + +test.must_match(['work1', 'sub21', 'f21.out'], "work1/sub21/f21.in\n") +test.must_match(['work1', 'sub22', 'f22.out'], "work1/sub22/f22.in\n") +test.must_match(['work1', 'sub23', 'f23.out'], "work1/sub23/f23.in\n") + + + +test.subdir('work2', + ['work2', 'sub']) + +work2 = test.workpath('work2') +work2_sub_f1_out = test.workpath('work2', 'sub', 'f1.out') +work2_sub_f2_out = test.workpath('work2', 'sub', 'f2.out') + +test.write(['work2', 'SConstruct'], """\ +cat_command = r"%(python)s %(cat_py)s ${TARGET.file} ${SOURCE.file}" +env = Environment() +env.Command('sub/f1.out', 'sub/f1.in', cat_command, + chdir=1) +env.Command('sub/f2.out', 'sub/f2.in', + [ + r"%(python)s %(cat_py)s .temp ${SOURCE.file}", + r"%(python)s %(cat_py)s ${TARGET.file} .temp", + ], + chdir=1) +""" % locals()) + +test.write(['work2', 'sub', 'f1.in'], "work2/sub/f1.in") +test.write(['work2', 'sub', 'f2.in'], "work2/sub/f2.in") + +expect = test.wrap_stdout("""\ +os.chdir('sub') +%(python)s %(cat_py)s f1.out f1.in +os.chdir('%(work2)s') +os.chdir('sub') +%(python)s %(cat_py)s .temp f2.in +%(python)s %(cat_py)s f2.out .temp +os.chdir('%(work2)s') +""" % locals()) + +test.run(chdir='work2', arguments='-n .', stdout=expect) + +test.must_not_exist(work2_sub_f1_out) +test.must_not_exist(work2_sub_f2_out) + +test.run(chdir='work2', arguments='.', stdout=expect) + +test.must_match(work2_sub_f1_out, "work2/sub/f1.in") +test.must_match(work2_sub_f2_out, "work2/sub/f2.in") + +test.run(chdir='work2', arguments='-c .') + +test.must_not_exist(work2_sub_f1_out) +test.must_not_exist(work2_sub_f2_out) + +test.run(chdir='work2', arguments='-s .', stdout="") + +test.must_match(work2_sub_f1_out, "work2/sub/f1.in") +test.must_match(work2_sub_f2_out, "work2/sub/f2.in") + +test.pass_test() diff --git a/test/strfunction.py b/test/strfunction.py index c8b2665..6754596 100644 --- a/test/strfunction.py +++ b/test/strfunction.py @@ -135,9 +135,6 @@ Building lazystr.out from lazystr.in %s cat.py list.in .temp %s cat.py .temp list.out Building liststr.out from liststr.in -Building liststr.out from liststr.in """) % (python, python, python, python, python, python, python, python)) -# XXX The duplication of "Buiding liststr.out" above is WRONG! -# A follow-on fix should take care of this. test.pass_test() -- cgit v0.12