From: stevenknight Date: Fri, 11 Jan 2002 07:03:58 +0000 (+0000) Subject: Add command-line redirection (Charles Crain). X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=ca5c7675899e22064205bf8dcdde8a3b92f126a2;p=scons.git Add command-line redirection (Charles Crain). git-svn-id: http://scons.tigris.org/svn/scons/trunk@201 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- 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() +