summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-09-27 21:23:04 (GMT)
committerSteven Knight <knight@baldmt.com>2004-09-27 21:23:04 (GMT)
commit2af9b1c17b019d339ad31db5ca310023b1448c40 (patch)
treef43576c3c7ddce67d169ed799471c6d385b5cbba
parent4b86ecf345ec8e865e530cb6d011b7c6c1cee93b (diff)
downloadSCons-2af9b1c17b019d339ad31db5ca310023b1448c40.zip
SCons-2af9b1c17b019d339ad31db5ca310023b1448c40.tar.gz
SCons-2af9b1c17b019d339ad31db5ca310023b1448c40.tar.bz2
Add support for changing directory when executing Actions (the to the target directory by default).
-rw-r--r--doc/man/scons.1158
-rw-r--r--src/CHANGES.txt4
-rw-r--r--src/engine/SCons/Action.py76
-rw-r--r--src/engine/SCons/ActionTests.py64
-rw-r--r--src/engine/SCons/Builder.py24
-rw-r--r--src/engine/SCons/Executor.py16
-rw-r--r--src/engine/SCons/ExecutorTests.py15
-rw-r--r--src/engine/SCons/Node/FS.py4
-rw-r--r--src/engine/SCons/Node/FSTests.py16
-rw-r--r--test/SConscriptChdir.py85
-rw-r--r--test/chdir.py276
-rw-r--r--test/strfunction.py3
12 files changed, 638 insertions, 103 deletions
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()