Add command-line redirection (Charles Crain).
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 11 Jan 2002 07:03:58 +0000 (07:03 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 11 Jan 2002 07:03:58 +0000 (07:03 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@201 fdb21ef1-2011-0410-befe-b5e4ea1792b1

config
doc/man/scons.1
runtest.py
src/CHANGES.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Script/SConscript.py
test/build-errors.py
test/redirection.py [new file with mode: 0644]

diff --git a/config b/config
index 44e99ce4c7658a3b7cefb150cc3830f704891e0a..445a1b13a2fb4265ad36d7e5bb57efb2836cb549 100644 (file)
--- 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";
 
index c249162f7a5f408dc4f6e032db976e73afec83e2..3f4af0690b00f9bfcd19073f105a2ef168a90999 100644 (file)
@@ -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
index 4535064a1a2d1d5afa0f0931a19adbd2fa42f01e..4e4ed0c27c92f8699c41369947ecbb444cf80d38 100644 (file)
@@ -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
index 43860f10a25e1ede1ea75c1760f446d905735ed0..17cc20a14dd28aa895eff95b214551cb8a02e26e 100644 (file)
@@ -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
index 7e53c2b3247d2c0c5edeb5f9a87bed8c99f48263..38a4c70511007c6e7435810a59f872da28ffd356 100644 (file)
@@ -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."""
index c01f47c0c65c0f2abec784d259e1c1f6bb2e74b8..a70fd998f80d7dfe02df7f810d8214bede342429 100644 (file)
@@ -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
         """
index 316fe307a191acebd9a91389995005dbcd5b45c2..e383ee14a3580def8c440d423ef7f1e604709cf2 100644 (file)
@@ -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
index 7085547c9a114693d2b8be08f0e283af74fdb3a0..cb93d6419b6798e0c43ff685505b7e48f918952b 100644 (file)
@@ -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
index 9f09ea02312cefb7de1da6954657ca87673f5cb4..85456422898a7720be640f06f1f600a9dde0c017 100644 (file)
@@ -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 (file)
index 0000000..1cd3f60
--- /dev/null
@@ -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()
+