source: rtems-tools/specbuilder/specbuilder/execute.py @ 68c9b8a

4.104.115
Last change on this file since 68c9b8a was 68c9b8a, checked in by Chris Johns <chrisj@…>, on 08/04/12 at 12:29:24

Remove CVS Id and update the copyright year.

  • Property mode set to 100755
File size: 14.2 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2012 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# RTEMS Tools is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# RTEMS Tools is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with RTEMS Tools.  If not, see <http://www.gnu.org/licenses/>.
20#
21
22#
23# Execute commands or scripts.
24#
25# Note, the subprocess module is only in Python 2.4 or higher.
26#
27
28import os
29import re
30import sys
31import subprocess
32import threading
33
34import error
35import log
36
37# Redefine the PIPE from subprocess
38PIPE = subprocess.PIPE
39
40# Regular expression to find quotes.
41qstr = re.compile('[rR]?\'([^\\n\'\\\\]|\\\\.)*\'|[rR]?"([^\\n"\\\\]|\\\\.)*"')
42
43def check_type(command):
44    """Checks the type of command we have. The types are spawn and
45    shell."""
46    if command in ['spawn', 'shell']:
47        return True
48    return False
49
50def arg_list(args):
51    """Turn a string of arguments into a list suitable for
52    spawning a command. If the args are already a list return
53    it."""
54    if type(args) is list:
55        return args
56    argstr = args
57    args = []
58    while len(argstr):
59        qs = qstr.search(argstr)
60        if not qs:
61            args.extend(argstr.split())
62            argstr= ''
63        else:
64            # We have a quoted string. Get the string before
65            # the quoted string and splt on white space then
66            # add the quoted string as an option then remove
67            # the first + quoted string and try again
68            front = argstr[:qs.start()]
69            args.extend(front.split())
70            args.append(argstr[qs.start() + 1:qs.end() - 1])
71            argstr = argstr[qs.end():]
72    return args
73
74def arg_subst(command, substs):
75    """Substitute the %[0-9] in the command with the subst values."""
76    args = arg_list(command)
77    if substs:
78        for a in range(0, len(args)):
79            for r in range(0, len(substs)):
80                args[a] = re.compile(('%%%d' % (r))).sub(substs[r], args[a])
81    return args
82
83def arg_subst_str(command, subst):
84    cmd = arg_subst(command, subst)
85    def add(x, y): return x + ' ' + str(y)
86    return reduce(add, cmd, '')
87
88class execute:
89    """Execute commands or scripts. The 'output' is a funtion
90    that handles the output from the process."""
91    def __init__(self, output = None, error_prefix = '', verbose = False):
92        self.output = output
93        self.error_prefix = error_prefix
94        self.verbose = verbose
95        self.shell_exe = None
96        self.shell_commands = False
97        self.path = None
98        self.environment = None
99
100    def capture(self, proc, timeout = None):
101        """Create 2 threads to read stdout and stderr and send to the
102        output handler. Based on the 'communicate' code in the subprocess
103        module."""
104        def _readthread(fh, out, prefix = ''):
105            """Read from a file handle and write to the output handler
106            until the file closes."""
107            while True:
108                line = fh.readline()
109                if len(line) == 0:
110                    break
111                if out:
112                    out(prefix + line)
113                else:
114                    log.output(prefix + line)
115        def _timerthread(proc, timer):
116            """Timer thread calls the timer handler if one
117            is present once a second. The user provides a handler
118            and returns False to kill the process or True continue."""
119            while True:
120                time.sleep(1)
121                if not timer(proc):
122                    proc.stdout.close()
123                    proc.stderr.close()
124
125        if proc.stdout:
126            stdout_thread = threading.Thread(target = _readthread,
127                                             args = (proc.stdout,
128                                                     self.output,
129                                                     ''))
130            stdout_thread.setDaemon(True)
131            stdout_thread.start()
132        if proc.stderr:
133            stderr_thread = threading.Thread(target = _readthread,
134                                             args = (proc.stderr,
135                                                     self.output,
136                                                     self.error_prefix))
137            stderr_thread.setDaemon(True)
138            stderr_thread.start()
139        if proc.stdout:
140            stdout_thread.join()
141        if proc.stderr:
142            stderr_thread.join()
143        return proc.wait()
144
145    def open(self, command, capture = True, shell = False,
146             cwd = None, env = None,
147             stdin = None, stdout = None, stderr = None):
148        """Open a command with arguments. Provide the arguments as a list or
149        a string."""
150        if self.verbose:
151            s = command
152            if type(command) is list:
153                def add(x, y): return x + ' ' + str(y)
154                s = reduce(add, command, '')[1:]
155            what = 'spawn'
156            if shell:
157                what = 'shell'
158            log.output(what + ': ' + s)
159        if shell and self.shell_exe:
160            command = arg_list(command)
161            command[:0] = self.shell_exe
162        if not stdout:
163            stdout = subprocess.PIPE
164        if not stderr:
165            stderr = subprocess.PIPE
166        proc = None
167        if cwd is None:
168            cwd = self.path
169        if env is None:
170            env = self.environment
171        try:
172            # Work around a problem on Windows with commands that
173            # have a '.' and no extension. Windows needs the full
174            # command name.
175            if sys.platform == "win32" and type(command) is list:
176                if command[0].find('.') >= 0:
177                    r, e = os.path.splitext(command[0])
178                    if e not in ['.exe', '.com', '.bat']:
179                        command[0] = command[0] + '.exe'
180            proc = subprocess.Popen(command, shell = shell,
181                                    cwd = cwd, env = env,
182                                    stdin = stdin, stdout = stdout,
183                                    stderr = stderr)
184            if not capture:
185                return (0, proc)
186            exit_code = self.capture(proc)
187            if self.verbose:
188                log.output('exit: ' + str(exit_code))
189        except OSError, ose:
190            exit_code = ose.errno
191            if self.verbose:
192                log.output('exit: ' + str(ose))
193        return (exit_code, proc)
194
195    def spawn(self, command, capture = True, cwd = None, env = None,
196              stdin = None, stdout = None, stderr = None):
197        """Spawn a command with arguments. Provide the arguments as a list or
198        a string."""
199        return self.open(command, capture, False, cwd, env,
200                         stdin, stdout, stderr)
201
202    def shell(self, command, capture = True, cwd = None, env = None,
203              stdin = None, stdout = None, stderr = None):
204        """Execute a command within a shell context. The command can contain
205        argumments. The shell is specific to the operating system. For example
206        it is cmd.exe on Windows XP."""
207        return self.open(command, capture, True, cwd, env,
208                         stdin, stdout, stderr)
209
210    def command(self, command, args = None, capture = True, shell = False,
211                cwd = None, env = None,
212                stdin = None, stdout = None, stderr = None):
213        """Run the command with the args. The args can be a list
214        or a string."""
215        if args and not type(args) is list:
216            args = arg_list(args)
217        cmd = [command]
218        if args:
219            cmd.extend(args)
220        return self.open(cmd, capture = capture, shell = shell,
221                         cwd = cwd, env = env,
222                         stdin = stdin, stdout = stdout, stderr = stderr)
223
224    def command_subst(self, command, substs, capture = True, shell = False,
225                      cwd = None, env = None,
226                      stdin = None, stdout = None, stderr = None):
227        """Run the command from the config data with the
228        option format string subsituted with the subst variables."""
229        args = arg_subst(command, substs)
230        return self.command(args[0], args[1:], capture = capture,
231                            shell = shell or self.shell_commands,
232                            cwd = cwd, env = env,
233                            stdin = stdin, stdout = stdout, stderr = stderr)
234
235    def set_shell(self, execute):
236        """Set the shell to execute when issuing a shell command."""
237        args = arg_list(execute)
238        if len(args) == 0 or not os.path.isfile(args[0]):
239            raise error.general('could find shell: ' + execute)
240        self.shell_exe = args
241
242    def command_use_shell(self):
243        """Force all commands to use a shell. This can be used with set_shell
244        to allow Unix commands be executed on Windows with a Unix shell such
245        as Cygwin or MSYS. This may cause piping to fail."""
246        self.shell_commands = True
247
248    def set_output(self, output):
249        """Set the output handler. The stdout of the last process in a pipe
250        line is passed to this handler."""
251        old_output = self.output
252        self.output = output
253        return old_output
254
255    def set_path(self, path):
256        """Set the path changed to before the child process is created."""
257        old_path = self.path
258        self.path = path
259        return old_path
260
261    def set_environ(self, environment):
262        """Set the environment passed to the child process when created."""
263        old_environment = self.environment
264        self.environment = environment
265        return old_environment
266
267class capture_execution(execute):
268    """Capture all output as a string and return it."""
269
270    class _output_snapper:
271        def __init__(self, log = None, dump = False):
272            self.output = ''
273            self.log = log
274            self.dump = dump
275
276        def handler(self, text):
277            if not self.dump:
278                if self.log is not None:
279                    self.log.output(text)
280                else:
281                    self.output += text
282
283        def get_and_clear(self):
284            text = self.output
285            self.output = ''
286            return text.strip()
287
288    def __init__(self, log = None, dump = False, error_prefix = '', verbose = False):
289        self.snapper = capture_execution._output_snapper(log = log, dump = dump)
290        execute.__init__(self, output = self.snapper.handler,
291                         error_prefix = error_prefix,
292                         verbose = verbose)
293
294    def open(self, command, capture = True, shell = False, cwd = None, env = None,
295             stdin = None, stdout = None, stderr = None):
296        if not capture:
297            raise error.general('output capture must be true; leave as default')
298        #self.snapper.get_and_clear()
299        exit_code, proc = execute.open(self, command, capture = True, shell = shell,
300                                       cwd = cwd, env = env,
301                                       stdin = stdin, stdout = stdout, stderr = stderr)
302        return (exit_code, proc, self.snapper.get_and_clear())
303
304    def set_output(self, output):
305        raise error.general('output capture cannot be overrided')
306
307if __name__ == "__main__":
308    def run_tests(e, commands, use_shell):
309        for c in commands['shell']:
310            e.shell(c)
311        for c in commands['spawn']:
312            e.spawn(c)
313        for c in commands['cmd']:
314            if type(c) is str:
315                e.command(c, shell = use_shell)
316            else:
317                e.command(c[0], c[1], shell = use_shell)
318        for c in commands['csubsts']:
319            e.command_subst(c[0], c[1], shell = use_shell)
320        ec, proc = e.command(commands['pipe'][0], commands['pipe'][1],
321                             capture = False, stdin = subprocess.PIPE)
322        if ec == 0:
323            print 'piping input into ' + commands['pipe'][0] + ': ' + \
324                  commands['pipe'][2]
325            proc.stdin.write(commands['pipe'][2])
326            proc.stdin.close()
327            e.capture(proc)
328            del proc
329
330    cmd_shell_test = 'if "%OS%" == "Windows_NT" (echo It is WinNT) else echo Is is not WinNT'
331    sh_shell_test = 'x="me"; if [ $x = "me" ]; then echo "It was me"; else "It was him"; fi'
332
333    commands = {}
334    commands['windows'] = {}
335    commands['unix'] = {}
336    commands['windows']['shell'] = ['cd', 'dir /w', '.\\xyz', cmd_shell_test]
337    commands['windows']['spawn'] = ['hostname', 'hostnameZZ', ['netstat', '/e']]
338    commands['windows']['cmd'] = [('ipconfig'), ('nslookup', 'www.python.org')]
339    commands['windows']['csubsts'] = [('netstat %0', ['-a']),
340                                      ('netstat %0 %1', ['-a', '-n'])]
341    commands['windows']['pipe'] = ('ftp', None, 'help\nquit')
342    commands['unix']['shell'] = ['pwd', 'ls -las', './xyz', sh_shell_test]
343    commands['unix']['spawn'] = ['ls', 'execute.pyc', ['ls', '-i']]
344    commands['unix']['cmd'] = [('date'), ('date', '-R'), ('date', ['-u', '+%d %D']),
345                               ('date', '-u "+%d %D %S"')]
346    commands['unix']['csubsts'] = [('date %0 "+%d %D %S"', ['-u']),
347                                   ('date %0 %1', ['-u', '+%d %D %S'])]
348    commands['unix']['pipe'] = ('grep', 'hello', 'hello world')
349
350    print arg_list('cmd a1 a2 "a3 is a string" a4')
351    print arg_list('cmd b1 b2 "b3 is a string a4')
352    print arg_subst(['nothing', 'xx-%0-yyy', '%1', '%2-something'],
353                    ['subst0', 'subst1', 'subst2'])
354
355    e = execute(error_prefix = 'ERR: ', verbose = True)
356    if sys.platform == "win32":
357        run_tests(e, commands['windows'], False)
358        if os.path.exists('c:\\msys\\1.0\\bin\\sh.exe'):
359            e.set_shell('c:\\msys\\1.0\\bin\\sh.exe --login -c')
360            commands['unix']['pipe'] = ('c:\\msys\\1.0\\bin\\grep',
361                                        'hello', 'hello world')
362            run_tests(e, commands['unix'], True)
363    else:
364        run_tests(e, commands['unix'], False)
365    del e
Note: See TracBrowser for help on using the repository browser.