source: rtems-tools/rtemstoolkit/execute.py @ 606c08c

4.10
Last change on this file since 606c08c was 606c08c, checked in by Sebastian Huber <sebastian.huber@…>, on Jan 8, 2016 at 6:44:52 AM

rtemstoolkit: msys2 compatibility

  • Property mode set to 100755
File size: 21.3 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2014 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# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31#
32# Execute commands or scripts.
33#
34# Note, the subprocess module is only in Python 2.4 or higher.
35#
36
37import os
38import re
39import sys
40import subprocess
41import threading
42import time
43
44from . import error
45from . import log
46
47# Trace exceptions
48trace_threads = False
49
50# Redefine the PIPE from subprocess
51PIPE = subprocess.PIPE
52
53# Regular expression to find quotes.
54qstr = re.compile('[rR]?\'([^\\n\'\\\\]|\\\\.)*\'|[rR]?"([^\\n"\\\\]|\\\\.)*"')
55
56def check_type(command):
57    """Checks the type of command we have. The types are spawn and
58    shell."""
59    if command in ['spawn', 'shell']:
60        return True
61    return False
62
63def arg_list(args):
64    """Turn a string of arguments into a list suitable for
65    spawning a command. If the args are already a list return
66    it."""
67    if type(args) is list:
68        return args
69    argstr = args
70    args = []
71    while len(argstr):
72        qs = qstr.search(argstr)
73        if not qs:
74            args.extend(argstr.split())
75            argstr= ''
76        else:
77            # We have a quoted string. Get the string before
78            # the quoted string and splt on white space then
79            # add the quoted string as an option then remove
80            # the first + quoted string and try again
81            front = argstr[:qs.start()]
82            args.extend(front.split())
83            args.append(argstr[qs.start() + 1:qs.end() - 1])
84            argstr = argstr[qs.end():]
85    return args
86
87def arg_subst(command, substs):
88    """Substitute the %[0-9] in the command with the subst values."""
89    args = arg_list(command)
90    if substs:
91        for a in range(0, len(args)):
92            for r in range(0, len(substs)):
93                args[a] = re.compile(('%%%d' % (r))).sub(substs[r], args[a])
94    return args
95
96def arg_subst_str(command, subst):
97    cmd = arg_subst(command, subst)
98    def add(x, y): return x + ' ' + str(y)
99    return reduce(add, cmd, '')
100
101class execute(object):
102    """Execute commands or scripts. The 'output' is a funtion that handles the
103    output from the process. The 'input' is a function that blocks and returns
104    data to be written to stdin"""
105    def __init__(self, output = None, input = None, cleanup = None,
106                 error_prefix = '', verbose = False):
107        self.lock = threading.Lock()
108        self.output = output
109        self.input = input
110        self.cleanup = cleanup
111        self.error_prefix = error_prefix
112        self.verbose = verbose
113        self.shell_exe = None
114        self.shell_commands = False
115        self.path = None
116        self.environment = None
117        self.outputting = False
118        self.timing_out = False
119        self.proc = None
120
121    def _capture(self, command, proc, timeout = None):
122        """Create 3 threads to read stdout and stderr and send to the output handler
123        and call an input handler is provided. Based on the 'communicate' code
124        in the subprocess module."""
125        def _writethread(exe, fh, input):
126            """Call the input handler and write it to the stdin. The input handler should
127            block and return None or False if this thread is to exit and True if this
128            is a timeout check."""
129            if trace_threads:
130                print('executte:_writethread: start')
131            try:
132                while True:
133                    lines = input()
134                    if type(lines) == str:
135                        try:
136                            fh.write(lines)
137                        except:
138                            break
139                    if lines == None or \
140                       lines == False or \
141                       (lines == True and fh.closed):
142                        break
143            except:
144                if trace_threads:
145                    print('executte:_writethread: exception')
146                pass
147            try:
148                fh.close()
149            except:
150                pass
151            if trace_threads:
152                print('executte:_writethread: finished')
153
154        def _readthread(exe, fh, out, prefix = ''):
155            """Read from a file handle and write to the output handler
156            until the file closes."""
157            def _output_line(line, exe, prefix, out, count):
158                #print 'LINE:%d: %s' % (count, line)
159                exe.lock.acquire()
160                #exe.outputting = True
161                exe.lock.release()
162                if out:
163                    out(prefix + line)
164                else:
165                    log.output(prefix + line)
166                    if count > 10:
167                        log.flush()
168
169            if trace_threads:
170                print('executte:_readthread: start')
171            count = 0
172            line = ''
173            try:
174                while True:
175                    data = fh.read(1)
176                    if len(data) == 0:
177                        break
178                    #print '))))) %02x "%s"' % (ord(data), data)
179                    for c in data:
180                        line += c
181                        if c == '\n':
182                            count += 1
183                            _output_line(line, exe, prefix, out, count)
184                            if count > 10:
185                                count = 0
186                            line = ''
187            except:
188                raise
189                if trace_threads:
190                    print('executte:_readthread: exception')
191                pass
192            try:
193                fh.close()
194            except:
195                pass
196            if len(line):
197                _output_line(line, exe, prefix, out, 100)
198            if trace_threads:
199                print('executte:_readthread: finished')
200
201        def _timerthread(exe, interval, function):
202            """Timer thread is used to timeout a process if no output is
203            produced for the timeout interval."""
204            count = interval
205            while exe.timing_out:
206                time.sleep(1)
207                if count > 0:
208                    count -= 1
209                exe.lock.acquire()
210                if exe.outputting:
211                    count = interval
212                    exe.outputting = False
213                exe.lock.release()
214                if count == 0:
215                    try:
216                        proc.kill()
217                    except:
218                        pass
219                    else:
220                        function()
221                    break
222
223        name = os.path.basename(command[0])
224
225        stdin_thread = None
226        stdout_thread = None
227        stderr_thread = None
228        timeout_thread = None
229
230        if proc.stdout:
231            stdout_thread = threading.Thread(target = _readthread,
232                                             name = '_stdout[%s]' % (name),
233                                             args = (self,
234                                                     proc.stdout,
235                                                     self.output,
236                                                     ''))
237            stdout_thread.daemon = True
238            stdout_thread.start()
239        if proc.stderr:
240            stderr_thread = threading.Thread(target = _readthread,
241                                             name = '_stderr[%s]' % (name),
242                                             args = (self,
243                                                     proc.stderr,
244                                                     self.output,
245                                                     self.error_prefix))
246            stderr_thread.daemon = True
247            stderr_thread.start()
248        if self.input and proc.stdin:
249            stdin_thread = threading.Thread(target = _writethread,
250                                            name = '_stdin[%s]' % (name),
251                                            args = (self,
252                                                    proc.stdin,
253                                                    self.input))
254            stdin_thread.daemon = True
255            stdin_thread.start()
256        if timeout:
257            self.timing_out = True
258            timeout_thread = threading.Thread(target = _timerthread,
259                                              name = '_timeout[%s]' % (name),
260                                              args = (self,
261                                                      timeout[0],
262                                                      timeout[1]))
263            timeout_thread.daemon = True
264            timeout_thread.start()
265        try:
266            self.lock.acquire()
267            try:
268                self.proc = proc
269            except:
270                raise
271            finally:
272                self.lock.release()
273            exitcode = proc.wait()
274        except:
275            proc.kill()
276            raise
277        finally:
278            self.lock.acquire()
279            try:
280                self.proc = None
281            except:
282                raise
283            finally:
284                self.lock.release()
285            if self.cleanup:
286                self.cleanup(proc)
287            if timeout_thread:
288                self.timing_out = False
289                timeout_thread.join(10)
290            if stdin_thread:
291                stdin_thread.join(2)
292            if stdout_thread:
293                stdout_thread.join(2)
294            if stderr_thread:
295                stderr_thread.join(2)
296        return exitcode
297
298    def open(self, command, capture = True, shell = False,
299             cwd = None, env = None,
300             stdin = None, stdout = None, stderr = None,
301             timeout = None):
302        """Open a command with arguments. Provide the arguments as a list or
303        a string."""
304        if self.verbose:
305            s = command
306            if type(command) is list:
307                def add(x, y): return x + ' ' + str(y)
308                s = reduce(add, command, '')[1:]
309            what = 'spawn'
310            if shell:
311                what = 'shell'
312            log.output(what + ': ' + s)
313        if shell and self.shell_exe:
314            command = arg_list(command)
315            command[:0] = self.shell_exe
316        if not stdin and self.input:
317            stdin = subprocess.PIPE
318        if not stdout:
319            stdout = subprocess.PIPE
320        if not stderr:
321            stderr = subprocess.PIPE
322        proc = None
323        if cwd is None:
324            cwd = self.path
325        if env is None:
326            env = self.environment
327        try:
328            # Work around a problem on Windows with commands that
329            # have a '.' and no extension. Windows needs the full
330            # command name.
331            if sys.platform == "win32" and type(command) is list:
332                if command[0].find('.') >= 0:
333                    r, e = os.path.splitext(command[0])
334                    if e not in ['.exe', '.com', '.bat']:
335                        command[0] = command[0] + '.exe'
336            log.trace('exe: %s' % (command))
337            proc = subprocess.Popen(command, shell = shell,
338                                    cwd = cwd, env = env,
339                                    stdin = stdin, stdout = stdout,
340                                    stderr = stderr)
341            if not capture:
342                return (0, proc)
343            if self.output is None:
344                raise error.general('capture needs an output handler')
345            exit_code = self._capture(command, proc, timeout)
346            if self.verbose:
347                log.output('exit: ' + str(exit_code))
348        except OSError as ose:
349            exit_code = ose.errno
350            if self.verbose:
351                log.output('exit: ' + str(ose))
352        return (exit_code, proc)
353
354    def spawn(self, command, capture = True, cwd = None, env = None,
355              stdin = None, stdout = None, stderr = None,
356              timeout = None):
357        """Spawn a command with arguments. Provide the arguments as a list or
358        a string."""
359        return self.open(command, capture, False, cwd, env,
360                         stdin, stdout, stderr, timeout)
361
362    def shell(self, command, capture = True, cwd = None, env = None,
363              stdin = None, stdout = None, stderr = None,
364              timeout = None):
365        """Execute a command within a shell context. The command can contain
366        argumments. The shell is specific to the operating system. For example
367        it is cmd.exe on Windows XP."""
368        return self.open(command, capture, True, cwd, env,
369                         stdin, stdout, stderr, timeout)
370
371    def command(self, command, args = None, capture = True, shell = False,
372                cwd = None, env = None,
373                stdin = None, stdout = None, stderr = None,
374                timeout = None):
375        """Run the command with the args. The args can be a list
376        or a string."""
377        if args and not type(args) is list:
378            args = arg_list(args)
379        cmd = [command]
380        if args:
381            cmd.extend(args)
382        return self.open(cmd, capture = capture, shell = shell,
383                         cwd = cwd, env = env,
384                         stdin = stdin, stdout = stdout, stderr = stderr,
385                         timeout = timeout)
386
387    def command_subst(self, command, substs, capture = True, shell = False,
388                      cwd = None, env = None,
389                      stdin = None, stdout = None, stderr = None,
390                      timeout = None):
391        """Run the command from the config data with the
392        option format string subsituted with the subst variables."""
393        args = arg_subst(command, substs)
394        return self.command(args[0], args[1:], capture = capture,
395                            shell = shell or self.shell_commands,
396                            cwd = cwd, env = env,
397                            stdin = stdin, stdout = stdout, stderr = stderr,
398                            itmeout = timeout)
399
400    def set_shell(self, execute):
401        """Set the shell to execute when issuing a shell command."""
402        args = arg_list(execute)
403        if len(args) == 0 or not os.path.isfile(args[0]):
404            raise error.general('could find shell: ' + execute)
405        self.shell_exe = args
406
407    def command_use_shell(self):
408        """Force all commands to use a shell. This can be used with set_shell
409        to allow Unix commands be executed on Windows with a Unix shell such
410        as Cygwin or MSYS. This may cause piping to fail."""
411        self.shell_commands = True
412
413    def set_output(self, output):
414        """Set the output handler. The stdout of the last process in a pipe
415        line is passed to this handler."""
416        old_output = self.output
417        self.output = output
418        return old_output
419
420    def set_path(self, path):
421        """Set the path changed to before the child process is created."""
422        old_path = self.path
423        self.path = path
424        return old_path
425
426    def set_environ(self, environment):
427        """Set the environment passed to the child process when created."""
428        old_environment = self.environment
429        self.environment = environment
430        return old_environment
431
432    def kill(self):
433        self.lock.acquire()
434        try:
435            if self.proc is not None:
436                self.proc.kill()
437        except:
438            raise
439        finally:
440            self.lock.release()
441
442    def terminate(self):
443        self.lock.acquire()
444        try:
445            if self.proc is not None:
446                self.proc.terminate()
447        except:
448            raise
449        finally:
450            self.lock.release()
451
452    def send_signal(self, signal):
453        self.lock.acquire()
454        try:
455            if self.proc is not None:
456                print("sending sig")
457                self.proc.send_signal(signal)
458        except:
459            raise
460        finally:
461            self.lock.release()
462
463class capture_execution(execute):
464    """Capture all output as a string and return it."""
465
466    class _output_snapper:
467        def __init__(self, log = None, dump = False):
468            self.output = ''
469            self.log = log
470            self.dump = dump
471
472        def handler(self, text):
473            if not self.dump:
474                if self.log is not None:
475                    self.log.output(text)
476                else:
477                    self.output += text
478
479        def get_and_clear(self):
480            text = self.output
481            self.output = ''
482            return text.strip()
483
484    def __init__(self, log = None, dump = False, error_prefix = '', verbose = False):
485        self.snapper = capture_execution._output_snapper(log = log, dump = dump)
486        execute.__init__(self, output = self.snapper.handler,
487                         error_prefix = error_prefix,
488                         verbose = verbose)
489
490    def open(self, command, capture = True, shell = False, cwd = None, env = None,
491             stdin = None, stdout = None, stderr = None, timeout = None):
492        if not capture:
493            raise error.general('output capture must be true; leave as default')
494        #self.snapper.get_and_clear()
495        exit_code, proc = execute.open(self, command, capture = True, shell = shell,
496                                       cwd = cwd, env = env,
497                                       stdin = stdin, stdout = stdout, stderr = stderr,
498                                       timeout = timeout)
499        return (exit_code, proc, self.snapper.get_and_clear())
500
501    def set_output(self, output):
502        raise error.general('output capture cannot be overrided')
503
504if __name__ == "__main__":
505    def run_tests(e, commands, use_shell):
506        for c in commands['shell']:
507            e.shell(c)
508        for c in commands['spawn']:
509            e.spawn(c)
510        for c in commands['cmd']:
511            if type(c) is str:
512                e.command(c, shell = use_shell)
513            else:
514                e.command(c[0], c[1], shell = use_shell)
515        for c in commands['csubsts']:
516            e.command_subst(c[0], c[1], shell = use_shell)
517        ec, proc = e.command(commands['pipe'][0], commands['pipe'][1],
518                             capture = False, stdin = subprocess.PIPE)
519        if ec == 0:
520            print('piping input into ' + commands['pipe'][0] + ': ' + \
521                  commands['pipe'][2])
522            proc.stdin.write(commands['pipe'][2])
523            proc.stdin.close()
524            e.capture(proc)
525            del proc
526
527    cmd_shell_test = 'if "%OS%" == "Windows_NT" (echo It is WinNT) else echo Is is not WinNT'
528    sh_shell_test = 'x="me"; if [ $x = "me" ]; then echo "It was me"; else "It was him"; fi'
529
530    commands = {}
531    commands['windows'] = {}
532    commands['unix'] = {}
533    commands['windows']['shell'] = ['cd', 'dir /w', '.\\xyz', cmd_shell_test]
534    commands['windows']['spawn'] = ['hostname', 'hostnameZZ', ['netstat', '/e']]
535    commands['windows']['cmd'] = [('ipconfig'), ('nslookup', 'www.python.org')]
536    commands['windows']['csubsts'] = [('netstat %0', ['-a']),
537                                      ('netstat %0 %1', ['-a', '-n'])]
538    commands['windows']['pipe'] = ('ftp', None, 'help\nquit')
539    commands['unix']['shell'] = ['pwd', 'ls -las', './xyz', sh_shell_test]
540    commands['unix']['spawn'] = ['ls', 'execute.pyc', ['ls', '-i']]
541    commands['unix']['cmd'] = [('date'), ('date', '-R'), ('date', ['-u', '+%d %D']),
542                               ('date', '-u "+%d %D %S"')]
543    commands['unix']['csubsts'] = [('date %0 "+%d %D %S"', ['-u']),
544                                   ('date %0 %1', ['-u', '+%d %D %S'])]
545    commands['unix']['pipe'] = ('grep', 'hello', 'hello world')
546
547    print(arg_list('cmd a1 a2 "a3 is a string" a4'))
548    print(arg_list('cmd b1 b2 "b3 is a string a4'))
549    print(arg_subst(['nothing', 'xx-%0-yyy', '%1', '%2-something'],
550                    ['subst0', 'subst1', 'subst2']))
551
552    e = execute(error_prefix = 'ERR: ', verbose = True)
553    if sys.platform == "win32":
554        run_tests(e, commands['windows'], False)
555        if os.path.exists('c:\\msys\\1.0\\bin\\sh.exe'):
556            e.set_shell('c:\\msys\\1.0\\bin\\sh.exe --login -c')
557            commands['unix']['pipe'] = ('c:\\msys\\1.0\\bin\\grep',
558                                        'hello', 'hello world')
559            run_tests(e, commands['unix'], True)
560    else:
561        run_tests(e, commands['unix'], False)
562    del e
Note: See TracBrowser for help on using the repository browser.