summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config2
-rw-r--r--doc/man/scons.124
-rw-r--r--runtest.py4
-rw-r--r--src/CHANGES.txt5
-rw-r--r--src/engine/SCons/Action.py73
-rw-r--r--src/engine/SCons/ActionTests.py17
-rw-r--r--src/engine/SCons/BuilderTests.py20
-rw-r--r--src/engine/SCons/Script/SConscript.py2
-rw-r--r--test/build-errors.py61
-rw-r--r--test/redirection.py62
10 files changed, 235 insertions, 35 deletions
diff --git a/config b/config
index 44e99ce4..445a1b13 100644
--- a/config
+++ b/config
@@ -242,7 +242,7 @@ diff_command =
* written to conform to Perl conventions) and Aegis' expectations.
* See the comments in the test.pl script itself for details.
*/
-test_command = "python ${Source runtest.py} -b aegis -q -v ${VERsion} ${File_Name}";
+test_command = "python ${Source runtest.py Absolute} -b aegis -q -v ${VERsion} ${File_Name}";
new_test_filename = "test/CHANGETHIS.py";
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index c249162f..3f4af069 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -989,6 +989,30 @@ can be a relative or absolute path.
is an optional directory that will be used as the parent directory.
+.TP
+.RI SetCommandHandler( function )
+
+This registers a user
+.I function
+as the handler
+for interpreting and executing command-line strings.
+The function must expect three arguments:
+
+.IP
+.nf
+def commandhandler(cmd, args, env):
+.PP
+.fi
+
+.I cmd
+is the path to the command to be executed.
+.I args
+is that arguments to the command.
+.I env
+is a dictionary of the environment variables
+in which the command should be executed.
+
+
.SH EXTENDING SCONS
.SS Builder Objects
.B scons
diff --git a/runtest.py b/runtest.py
index 4535064a..4e4ed0c2 100644
--- a/runtest.py
+++ b/runtest.py
@@ -150,9 +150,9 @@ for path in tests:
if printcmd:
print cmd
s = os.system(cmd)
- if s == 1:
+ if s == 1 or s == 256:
fail.append(path)
- elif s == 2:
+ elif s == 2 or s == 512:
no_result.append(path)
elif s != 0:
print "Unexpected exit status %d" % s
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 43860f10..17cc20a1 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -16,6 +16,11 @@ RELEASE 0.03 -
- Add the InstallAs() method.
+ - Execute commands through an external interpreter (sh, cmd.exe, or
+ command.com) to handle redirection metacharacters.
+
+ - Allow the user to supply a command handler.
+
From Steven Knight:
- Search both /usr/lib and /usr/local/lib for scons directories by
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 7e53c2b3..38a4c705 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -46,13 +46,18 @@ exitvalmap = {
if os.name == 'posix':
- def spawn(cmd, args, env):
+ def defaultSpawn(cmd, args, env):
pid = os.fork()
if not pid:
# Child process.
exitval = 127
+ args = [ 'sh', '-c' ] + \
+ [ string.join(map(lambda x: string.replace(str(x),
+ ' ',
+ r'\ '),
+ args)) ]
try:
- os.execvpe(cmd, args, env)
+ os.execvpe('sh', args, env)
except OSError, e:
exitval = exitvalmap[e[0]]
sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
@@ -77,6 +82,8 @@ elif os.name == 'nt':
f = cmd + e
if os.path.exists(f):
return f
+ else:
+ return cmd
else:
path = env['PATH']
if not SCons.Util.is_List(path):
@@ -94,19 +101,61 @@ elif os.name == 'nt':
f = f + ext
if os.path.exists(f):
return f
- return cmd
+ return None
+
+ # Attempt to find cmd.exe (for WinNT/2k/XP) or
+ # command.com for Win9x
- def spawn(cmd, args, env):
+ cmd_interp = ''
+ # First see if we can look in the registry...
+ if SCons.Util.can_read_reg:
try:
+ # Look for Windows NT system root
+ k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
+ 'Software\\Microsoft\\Windows NT\\CurrentVersion')
+ val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
+ cmd_interp = os.path.join(val, 'System32\\cmd.exe')
+ except SCons.Util.RegError:
try:
- ret = os.spawnvpe(os.P_WAIT, cmd, args, env)
- except AttributeError:
- cmd = pathsearch(cmd, env)
- ret = os.spawnve(os.P_WAIT, cmd, args, env)
- except OSError, e:
- ret = exitvalmap[e[0]]
- sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
- return ret
+ # Okay, try the Windows 9x system root
+ k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
+ 'Software\\Microsoft\\Windows\\CurrentVersion')
+ val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
+ cmd_interp = os.path.join(val, 'command.com')
+ except:
+ pass
+ if not cmd_interp:
+ cmd_interp = pathsearch('cmd', os.environ)
+ if not cmd_interp:
+ cmd_interp = pathsearch('command', os.environ)
+
+ # The upshot of all this is that, if you are using Python 1.5.2,
+ # you had better have cmd or command.com in your PATH when you run
+ # scons.
+
+ def defaultSpawn(cmd, args, env):
+ if not cmd_interp:
+ sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
+ return 127
+ else:
+ try:
+ args = [ cmd_interp, '/C' ] + args
+ ret = os.spawnve(os.P_WAIT, cmd_interp, args, env)
+ except OSError, e:
+ ret = exitvalmap[e[0]]
+ sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
+ return ret
+else:
+ def defaultSpawn(cmd, args, env):
+ sys.stderr.write("scons: Unknown os '%s', cannot spawn command interpreter.\n" % os.name)
+ sys.stderr.write("scons: Set your command handler with SetCommandHandler().\n")
+ return 127
+
+spawn = defaultSpawn
+
+def SetCommandHandler(func):
+ global spawn
+ spawn = func
def Action(act):
"""A factory for action objects."""
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index c01f47c0..a70fd998 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -106,8 +106,25 @@ class CommandActionTestCase(unittest.TestCase):
def test_execute(self):
"""Test executing a command Action
"""
+ self.test_set_handler()
pass
+ def test_set_handler(self):
+ """Test setting the command handler...
+ """
+ class Test:
+ def __init__(self):
+ self.executed = 0
+ t=Test()
+ def func(cmd, args, env, test=t):
+ test.executed = 1
+ return 0
+ SCons.Action.SetCommandHandler(func)
+ assert SCons.Action.spawn is func
+ a = SCons.Action.CommandAction("xyzzy")
+ a.execute()
+ assert t.executed == 1
+
def test_get_contents(self):
"""Test fetching the contents of a command Action
"""
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index 316fe307..e383ee14 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -297,28 +297,30 @@ class BuilderTestCase(unittest.TestCase):
c = test.read(outfile, 'r')
assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
- # Test that a nonexistent command returns 127
- builder = MyBuilder(action = python + "_XyZzY_", name="badcmd")
- r = builder.execute(out = outfile)
- assert r == 127, "r == %d" % r
-
if os.name == 'nt':
# NT treats execs of directories and non-executable files
# as "file not found" errors
- expect = 127
+ expect_nonexistent = 1
+ expect_nonexecutable = 1
else:
- expect = 126
+ expect_nonexistent = 127
+ expect_nonexecutable = 126
+
+ # Test that a nonexistent command returns 127
+ builder = MyBuilder(action = python + "_XyZzY_", name="badcmd")
+ r = builder.execute(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(out = outfile)
- assert r == expect, "r == %d" % r
+ 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(out = outfile)
- assert r == expect, "r == %d" % r
+ assert r == expect_nonexecutable, "r == %d" % r
def test_get_contents(self):
"""Test returning the signature contents of a Builder
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index 7085547c..cb93d641 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -36,6 +36,7 @@ import SCons.Defaults
import SCons.Node
import SCons.Node.FS
import SCons.Environment
+import SCons.Action
import string
import sys
@@ -176,4 +177,5 @@ def BuildDefaultGlobals():
globals['Return'] = Return
globals['Dir'] = SCons.Node.FS.default_fs.Dir
globals['File'] = SCons.Node.FS.default_fs.File
+ globals['SetCommandHandler'] = SCons.Action.SetCommandHandler
return globals
diff --git a/test/build-errors.py b/test/build-errors.py
index 9f09ea02..85456422 100644
--- a/test/build-errors.py
+++ b/test/build-errors.py
@@ -48,28 +48,56 @@ env.bld(target = 'f1', source = 'f1.in')
test.run(arguments='-f SConstruct1 .',
stdout = "%s f1.in f1\n" % no_such_file,
- stderr = """scons: %s: No such file or directory
+ stderr = None)
+
+bad_command = "Bad command or file name\n"
+
+unrecognized = """'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+ errs = [
+ bad_command,
+ unrecognized % (no_such_file, 'f1'),
+ unspecified % 'f1'
+ ]
+ test.fail_test(not test.stderr() in errs)
+else:
+ test.fail_test(test.stderr() != """sh: %s: No such file or directory
scons: *** [f1] Error 127
""" % no_such_file)
+
test.write('SConstruct2', r"""
bld = Builder(name = 'bld', action = '%s $SOURCES $TARGET')
env = Environment(BUILDERS = [bld])
env.bld(target = 'f2', source = 'f2.in')
""" % string.replace(not_executable, '\\', '\\\\'))
+test.run(arguments='-f SConstruct2 .',
+ stdout = "%s f2.in f2\n" % not_executable,
+ stderr = None)
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
if os.name == 'nt':
- expect = """scons: %s: No such file or directory
-scons: *** [f2] Error 127
-""" % not_executable
+ errs = [
+ bad_command,
+ unrecognized % (no_such_file, 'f2'),
+ unspecified % 'f2'
+ ]
+ test.fail_test(not test.stderr() in errs)
else:
- expect = """scons: %s: Permission denied
+ test.fail_test(test.stderr() != """sh: %s: Permission denied
scons: *** [f2] Error 126
-""" % not_executable
-
-test.run(arguments='-f SConstruct2 .',
- stdout = "%s f2.in f2\n" % not_executable,
- stderr = expect)
+""" % not_executable)
test.write('SConstruct3', r"""
bld = Builder(name = 'bld', action = '%s $SOURCES $TARGET')
@@ -79,7 +107,18 @@ env.bld(target = 'f3', source = 'f3.in')
test.run(arguments='-f SConstruct3 .',
stdout = "%s f3.in f3\n" % test.workdir,
- stderr = """scons: %s: Permission denied
+ stderr = None)
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+ errs = [
+ bad_command,
+ unrecognized % (no_such_file, 'f3'),
+ unspecified % 'f3'
+ ]
+ test.fail_test(not test.stderr() in errs)
+else:
+ test.fail_test(test.stderr() != """sh: %s: is a directory
scons: *** [f3] Error 126
""" % test.workdir)
diff --git a/test/redirection.py b/test/redirection.py
new file mode 100644
index 00000000..1cd3f602
--- /dev/null
+++ b/test/redirection.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 Steven Knight
+#
+# 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 os
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('cat.py', r"""
+import sys
+try:
+ input = open(sys.argv[1], 'r').read()
+except IndexError:
+ input = sys.stdin.read()
+sys.stdout.write(input)
+sys.exit(0)
+""")
+
+test.write('SConstruct', r"""
+env = Environment()
+env.Command(target='foo1', source='bar1',
+ action='python cat.py $SOURCES > $TARGET')
+env.Command(target='foo2', source='bar2',
+ action='python cat.py < $SOURCES > $TARGET')
+env.Command(target='foo3', source='bar3',
+ action='python cat.py $SOURCES | python cat.py > $TARGET')
+""")
+
+test.write('bar1', 'bar1\r\n')
+test.write('bar2', 'bar2\r\n')
+test.write('bar3', 'bar3\r\n')
+
+test.run(arguments='.')
+test.fail_test(test.read('foo1') != 'bar1\r\n')
+test.fail_test(test.read('foo2') != 'bar2\r\n')
+test.fail_test(test.read('foo3') != 'bar3\r\n')
+
+test.pass_test()
+