summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2005-04-26 04:14:40 (GMT)
committerSteven Knight <knight@baldmt.com>2005-04-26 04:14:40 (GMT)
commit68a29a4d7f4689ce6200b3e34441f2a8df9f88a0 (patch)
tree4469aa200175c39f9d91eb1f655997a2aa87ced5
parenta1792d6a51a10202c0462eba85febc7255d10abf (diff)
downloadSCons-68a29a4d7f4689ce6200b3e34441f2a8df9f88a0.zip
SCons-68a29a4d7f4689ce6200b3e34441f2a8df9f88a0.tar.gz
SCons-68a29a4d7f4689ce6200b3e34441f2a8df9f88a0.tar.bz2
More flexible (and Make-like) ignoring command exit status, and suppressing printing of a command.
-rw-r--r--doc/man/scons.158
-rw-r--r--src/engine/SCons/Action.py49
-rw-r--r--src/engine/SCons/ActionTests.py83
-rw-r--r--src/engine/SCons/Executor.py10
-rw-r--r--src/engine/SCons/Node/__init__.py8
-rw-r--r--test/ignore-command.py87
-rw-r--r--test/silent-command.py86
7 files changed, 351 insertions, 30 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index cb441ce..cb1334b 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -2765,10 +2765,20 @@ already specified in other Builder of function calls.)
Any other keyword arguments specified override any
same-named existing construction variables.
-Note that an action can be an external command,
+An action can be an external command,
specified as a string,
or a callable Python object;
-see "Action Objects," below.
+see "Action Objects," below,
+for more complete information.
+Also note that a string specifying an external command
+may be preceded by an
+.B @
+(at-sign)
+to suppress printing the command in question,
+or by a
+.B \-
+(hyphen)
+to ignore the exit status of the external command.
Examples:
.ES
@@ -8613,9 +8623,27 @@ the object is simply returned.
.IP String
If the first argument is a string,
a command-line Action is returned.
+Note that the command line string
+may be preceded by an
+.B @
+(at-sign)
+to suppress printing of the
+specified command line,
+or by a
+.B \-
+(hyphen)
+to ignore the exit status from
+the specified command.
+Examples:
.ES
Action('$CC -c -o $TARGET $SOURCES')
+
+# Doesn't print the line being executed.
+Action('@build $TARGET $SOURCES')
+
+# Ignores
+Action('-build $TARGET $SOURCES')
.EE
.\" XXX From Gary Ruben, 23 April 2002:
@@ -8805,6 +8833,32 @@ a = Action("build < ${SOURCE.file} > ${TARGET.file}",
chdir=1)
.EE
+The
+.BR Action ()
+global function
+also takes an
+.B exitstatfunc
+keyword argument
+which specifies a function
+that is passed the exit status
+(or return value)
+from the specified action
+and can return an arbitrary
+or modified value.
+This can be used, for example,
+to specify that an Action object's
+return value should be ignored
+and SCons should, therefore,
+consider that the action always suceeds:
+
+.ES
+def always_succeed(s):
+ # Always return 0, which indicates success.
+ return 0
+a = Action("build < ${SOURCE.file} > ${TARGET.file}",
+ exitstatfunc=always_succeed)
+.EE
+
.SS Miscellaneous Action Functions
.B scons
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 3b0230c..353ead3 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -124,6 +124,9 @@ def rfile(n):
except AttributeError:
return n
+def default_exitstatfunc(s):
+ return s
+
def _actionAppend(act1, act2):
# This function knows how to slap two actions together.
# Mainly, it handles ListActions by concatenating into
@@ -245,19 +248,22 @@ if not SCons.Memoize.has_metaclass:
class _ActionAction(ActionBase):
"""Base class for actions that create output objects."""
- def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw):
+ def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw):
if not strfunction is _null:
self.strfunction = strfunction
if presub is _null:
presub = print_actions_presub
self.presub = presub
self.chdir = chdir
+ if not exitstatfunc:
+ exitstatfunc = default_exitstatfunc
+ self.exitstatfunc = exitstatfunc
def print_cmd_line(self, s, target, source, env):
sys.stdout.write(s + "\n")
def __call__(self, target, source, env,
- errfunc=None,
+ exitstatfunc=_null,
presub=_null,
show=_null,
execute=_null,
@@ -266,6 +272,7 @@ class _ActionAction(ActionBase):
target = [target]
if not SCons.Util.is_List(source):
source = [source]
+ if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
if presub is _null: presub = self.presub
if show is _null: show = print_actions
if execute is _null: execute = execute_actions
@@ -304,8 +311,7 @@ class _ActionAction(ActionBase):
os.chdir(chdir)
try:
stat = self.execute(target, source, env)
- if stat and errfunc:
- errfunc(stat)
+ stat = exitstatfunc(stat)
finally:
if save_cwd:
os.chdir(save_cwd)
@@ -350,12 +356,32 @@ class CommandAction(_ActionAction):
return string.join(map(str, self.cmd_list), ' ')
return str(self.cmd_list)
+ def process(self, target, source, env):
+ result = env.subst_list(self.cmd_list, 0, target, source)
+ silent = None
+ ignore = None
+ while 1:
+ try: c = result[0][0][0]
+ except IndexError: c = None
+ if c == '@': silent = 1
+ elif c == '-': ignore = 1
+ else: break
+ result[0][0] = result[0][0][1:]
+ try:
+ if not result[0][0]:
+ result[0] = result[0][1:]
+ except IndexError:
+ pass
+ return result, ignore, silent
+
def strfunction(self, target, source, env):
if not self.cmdstr is None:
c = env.subst(self.cmdstr, 0, target, source)
if c:
return c
- cmd_list = env.subst_list(self.cmd_list, 0, target, source)
+ cmd_list, ignore, silent = self.process(target, source, env)
+ if silent:
+ return ''
return _string_from_cmd_list(cmd_list[0])
def execute(self, target, source, env):
@@ -406,15 +432,14 @@ class CommandAction(_ActionAction):
# reasonable for just about everything else:
ENV[key] = str(value)
- cmd_list = env.subst_list(self.cmd_list, 0, target,
- map(rfile, source))
+ cmd_list, ignore, silent = self.process(target, map(rfile, source), env)
# Use len() to filter out any "command" that's zero-length.
for cmd_line in filter(len, cmd_list):
# Escape the command line for the interpreter we are using.
cmd_line = escape_list(cmd_line, escape)
result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
- if result:
+ if not ignore and result:
return result
return 0
@@ -461,10 +486,10 @@ class CommandGeneratorAction(ActionBase):
def genstring(self, target, source, env):
return self._generate(target, source, env, 1).genstring(target, source, env)
- def __call__(self, target, source, env, errfunc=None, presub=_null,
+ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
show=_null, execute=_null, chdir=_null):
act = self._generate(target, source, env, 0)
- return act(target, source, env, errfunc, presub,
+ return act(target, source, env, exitstatfunc, presub,
show, execute, chdir)
def get_contents(self, target, source, env):
@@ -650,10 +675,10 @@ class ListAction(ActionBase):
self.list),
"")
- def __call__(self, target, source, env, errfunc=None, presub=_null,
+ def __call__(self, target, source, env, exitstatfunc=_null, presub=_null,
show=_null, execute=_null, chdir=_null):
for act in self.list:
- stat = act(target, source, env, errfunc, presub,
+ stat = act(target, source, env, exitstatfunc, presub,
show, execute, chdir)
if stat:
return stat
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 3790c8c..890abb2 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -58,7 +58,8 @@ import TestCmd
# for each test, they can just use the one.
test = TestCmd.TestCmd(workdir = '')
-test.write('act.py', """import os, string, sys
+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:
@@ -81,7 +82,13 @@ if os.environ.has_key( 'ACTPY_PIPE' ):
sys.exit(0)
""")
+test.write('exit.py', """\
+import sys
+sys.exit(int(sys.argv[1]))
+""")
+
act_py = test.workpath('act.py')
+exit_py = test.workpath('exit.py')
outfile = test.workpath('outfile')
outfile2 = test.workpath('outfile2')
@@ -319,7 +326,6 @@ class ActionBaseTestCase(unittest.TestCase):
pass
class _ActionActionTestCase(unittest.TestCase):
-
def test__init__(self):
"""Test creation of _ActionAction objects
@@ -527,18 +533,19 @@ class _ActionActionTestCase(unittest.TestCase):
assert s == '', s
sys.stdout = save_stdout
- errfunc_result = []
+ exitstatfunc_result = []
- def errfunc(stat, result=errfunc_result):
+ def exitstatfunc(stat, result=exitstatfunc_result):
result.append(stat)
+ return stat
- result = a("out", "in", env, errfunc=errfunc)
+ result = a("out", "in", env, exitstatfunc=exitstatfunc)
assert result == 0, result
- assert errfunc_result == [], errfunc_result
+ assert exitstatfunc_result == [], exitstatfunc_result
- result = a("out", "in", env, execute=1, errfunc=errfunc)
+ result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc)
assert result == 7, result
- assert errfunc_result == [7], errfunc_result
+ assert exitstatfunc_result == [7], exitstatfunc_result
SCons.Action.execute_actions = 1
@@ -830,6 +837,38 @@ class CommandActionTestCase(unittest.TestCase):
s = act4.strfunction([t1], [s1], env)
assert s is None, s
+ act = SCons.Action.CommandAction("@foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "", s
+
+ act = SCons.Action.CommandAction("@-foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "", s
+
+ act = SCons.Action.CommandAction("-@foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "", s
+
+ act = SCons.Action.CommandAction("-foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "foo bar", s
+
+ act = SCons.Action.CommandAction("@ foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "", s
+
+ act = SCons.Action.CommandAction("@- foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "", s
+
+ act = SCons.Action.CommandAction("-@ foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "", s
+
+ act = SCons.Action.CommandAction("- foo bar")
+ s = act.strfunction([], [], env)
+ assert s == "foo bar", s
+
def test_execute(self):
"""Test execution of command Actions
@@ -952,6 +991,34 @@ class CommandActionTestCase(unittest.TestCase):
r = act([], [], env.Copy(out = outfile))
assert r == expect_nonexecutable, "r == %d" % r
+ act = SCons.Action.CommandAction('%s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 1, r
+
+ act = SCons.Action.CommandAction('@%s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 1, r
+
+ act = SCons.Action.CommandAction('@-%s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 0, r
+
+ act = SCons.Action.CommandAction('-%s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 0, r
+
+ act = SCons.Action.CommandAction('@ %s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 1, r
+
+ act = SCons.Action.CommandAction('@- %s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 0, r
+
+ act = SCons.Action.CommandAction('- %s %s 1' % (python, exit_py))
+ r = act([], [], env)
+ assert r == 0, r
+
def _DO_NOT_EXECUTE_test_pipe_execute(self):
"""Test capturing piped output from an action
diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py
index afe817a..afd6c49 100644
--- a/src/engine/SCons/Executor.py
+++ b/src/engine/SCons/Executor.py
@@ -93,21 +93,21 @@ class Executor:
result.update(kw)
return result
- def do_nothing(self, target, errfunc, kw):
+ def do_nothing(self, target, exitstatfunc, kw):
pass
- def do_execute(self, target, errfunc, kw):
+ def do_execute(self, target, exitstatfunc, kw):
"""Actually execute the action list."""
apply(self.action,
- (self.targets, self.sources, self.get_build_env(), errfunc),
+ (self.targets, self.sources, self.get_build_env(), exitstatfunc),
self.get_kw(kw))
# use extra indirection because with new-style objects (Python 2.2
# and above) we can't override special methods, and nullify() needs
# to be able to do this.
- def __call__(self, target, errfunc, **kw):
- self.do_execute(target, errfunc, kw)
+ def __call__(self, target, exitstatfunc, **kw):
+ self.do_execute(target, exitstatfunc, kw)
def cleanup(self):
"__reset_cache__"
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 28f1c19..f9390ea 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -227,10 +227,12 @@ class Node:
so only do thread safe stuff here. Do thread unsafe stuff in
built().
"""
- def errfunc(stat, node=self):
- raise SCons.Errors.BuildError(node=node, errstr="Error %d" % stat)
+ def exitstatfunc(stat, node=self):
+ if stat:
+ msg = "Error %d" % stat
+ raise SCons.Errors.BuildError(node=node, errstr=msg)
executor = self.get_executor()
- apply(executor, (self, errfunc), kw)
+ apply(executor, (self, exitstatfunc), kw)
def built(self):
"""Called just after this node is successfully built."""
diff --git a/test/ignore-command.py b/test/ignore-command.py
new file mode 100644
index 0000000..201c488
--- /dev/null
+++ b/test/ignore-command.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001, 2002, 2003, 2004 The SCons Foundation
+#
+# 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.
+#
+
+"""
+Test use of a preceding - to ignore the return value from a command.
+"""
+
+__revision__ = "/home/scons/scons/branch.0/branch.96/baseline/test/option-n.py 0.96.C352 2005/03/26 00:09:23 knight"
+
+import os
+import os.path
+import re
+import string
+import sys
+import TestCmd
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+test.subdir('build', 'src')
+
+test.write('build.py', r"""
+import sys
+fp = open(sys.argv[1], 'wb')
+for f in sys.argv[2:]:
+ fp.write(open(f, 'rb').read())
+fp.close()
+sys.exit(1)
+""")
+
+test.write('SConstruct', """\
+env = Environment()
+f1 = env.Command('f1.out', 'f1.in', "%(python)s build.py $TARGET $SOURCE")
+f2 = env.Command('f2.out', 'f2.in', "-%(python)s build.py $TARGET $SOURCE")
+f3 = env.Command('f3.out', 'f3.in', "- %(python)s build.py $TARGET $SOURCE")
+f4 = env.Command('f4.out', 'f4.in', "@-%(python)s build.py $TARGET $SOURCE")
+f5 = env.Command('f5.out', 'f5.in', "@- %(python)s build.py $TARGET $SOURCE")
+f6 = env.Command('f6.out', 'f6.in', "-@%(python)s build.py $TARGET $SOURCE")
+f7 = env.Command('f7.out', 'f7.in', "-@ %(python)s build.py $TARGET $SOURCE")
+Default(f2, f3, f4, f5, f6, f7)
+""" % locals())
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+test.write('f7.in', "f7.in\n")
+
+test.run()
+
+test.must_match('f2.out', "f2.in\n")
+test.must_match('f3.out', "f3.in\n")
+test.must_match('f4.out', "f4.in\n")
+test.must_match('f5.out', "f5.in\n")
+test.must_match('f6.out', "f6.in\n")
+test.must_match('f7.out', "f7.in\n")
+
+test.run(arguments='.', status=2, stderr=None)
+
+test.must_match('f1.out', "f1.in\n")
+
+test.pass_test()
diff --git a/test/silent-command.py b/test/silent-command.py
new file mode 100644
index 0000000..a5da7a8
--- /dev/null
+++ b/test/silent-command.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001, 2002, 2003, 2004 The SCons Foundation
+#
+# 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.
+#
+
+"""
+Test the use of a preceding @ to suppress printing a command.
+"""
+
+__revision__ = "/home/scons/scons/branch.0/branch.96/baseline/test/option-n.py 0.96.C352 2005/03/26 00:09:23 knight"
+
+import os
+import os.path
+import re
+import string
+import sys
+import TestCmd
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+test.subdir('build', 'src')
+
+test.write('build.py', r"""
+import sys
+fp = open(sys.argv[1], 'wb')
+for f in sys.argv[2:]:
+ fp.write(open(f, 'rb').read())
+fp.close()
+""")
+
+test.write('SConstruct', """\
+env = Environment()
+env.Command('f1.out', 'f1.in', "%(python)s build.py $TARGET $SOURCE")
+env.Command('f2.out', 'f2.in', "@%(python)s build.py $TARGET $SOURCE")
+env.Command('f3.out', 'f3.in', "@ %(python)s build.py $TARGET $SOURCE")
+env.Command('f4.out', 'f4.in', "@-%(python)s build.py $TARGET $SOURCE")
+env.Command('f5.out', 'f5.in', "@- %(python)s build.py $TARGET $SOURCE")
+env.Command('f6.out', 'f6.in', "-@%(python)s build.py $TARGET $SOURCE")
+env.Command('f7.out', 'f7.in', "-@ %(python)s build.py $TARGET $SOURCE")
+""" % locals())
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+test.write('f7.in', "f7.in\n")
+
+expect = test.wrap_stdout("""\
+%(python)s build.py f1.out f1.in
+""" % locals())
+
+test.run(arguments = '.', stdout = expect)
+
+test.must_match('f1.out', "f1.in\n")
+test.must_match('f2.out', "f2.in\n")
+test.must_match('f3.out', "f3.in\n")
+test.must_match('f4.out', "f4.in\n")
+test.must_match('f5.out', "f5.in\n")
+test.must_match('f6.out', "f6.in\n")
+test.must_match('f7.out', "f7.in\n")
+
+test.pass_test()