| # Copyright 2006-2008 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Shared process-related utility functions.""" |
| |
| from __future__ import print_function |
| |
| import errno |
| import os |
| import subprocess |
| import sys |
| |
| class CommandNotFound(Exception): pass |
| |
| |
| TASKKILL = os.path.join(os.environ['WINDIR'], 'system32', 'taskkill.exe') |
| TASKKILL_PROCESS_NOT_FOUND_ERR = 128 |
| # On windows 2000 there is no taskkill.exe, we need to have pskill somewhere |
| # in the path. |
| PSKILL = 'pskill.exe' |
| PSKILL_PROCESS_NOT_FOUND_ERR = -1 |
| |
| def KillAll(executables): |
| """Tries to kill all copies of each process in the processes list. Returns |
| an error if any running processes couldn't be killed. |
| """ |
| result = 0 |
| if os.path.exists(TASKKILL): |
| command = [TASKKILL, '/f', '/im'] |
| process_not_found_err = TASKKILL_PROCESS_NOT_FOUND_ERR |
| else: |
| command = [PSKILL, '/t'] |
| process_not_found_err = PSKILL_PROCESS_NOT_FOUND_ERR |
| |
| for name in executables: |
| new_error = RunCommand(command + [name]) |
| # Ignore "process not found" error. |
| if new_error != 0 and new_error != process_not_found_err: |
| result = new_error |
| return result |
| |
| def RunCommandFull(command, verbose=True, collect_output=False, |
| print_output=True): |
| """Runs the command list. |
| |
| Prints the given command (which should be a list of one or more strings). |
| If specified, prints its stderr (and optionally stdout) to stdout, |
| line-buffered, converting line endings to CRLF (see note below). If |
| specified, collects the output as a list of lines and returns it. Waits |
| for the command to terminate and returns its status. |
| |
| Args: |
| command: the full command to run, as a list of one or more strings |
| verbose: if True, combines all output (stdout and stderr) into stdout. |
| Otherwise, prints only the command's stderr to stdout. |
| collect_output: if True, collects the output of the command as a list of |
| lines and returns it |
| print_output: if True, prints the output of the command |
| |
| Returns: |
| A tuple consisting of the process's exit status and output. If |
| collect_output is False, the output will be []. |
| |
| Raises: |
| CommandNotFound if the command executable could not be found. |
| """ |
| print( |
| '\n' + subprocess.list2cmdline(command).replace('\\', '/') + '\n', |
| end=' ') |
| |
| if verbose: |
| out = subprocess.PIPE |
| err = subprocess.STDOUT |
| else: |
| out = file(os.devnull, 'w') |
| err = subprocess.PIPE |
| try: |
| proc = subprocess.Popen(command, stdout=out, stderr=err, bufsize=1) |
| except OSError, e: |
| if e.errno == errno.ENOENT: |
| raise CommandNotFound('Unable to find "%s"' % command[0]) |
| raise |
| |
| output = [] |
| |
| if verbose: |
| read_from = proc.stdout |
| else: |
| read_from = proc.stderr |
| line = read_from.readline() |
| while line: |
| line = line.rstrip() |
| |
| if collect_output: |
| output.append(line) |
| |
| if print_output: |
| # Windows Python converts \n to \r\n automatically whenever it |
| # encounters it written to a text file (including stdout). The only |
| # way around it is to write to a binary file, which isn't feasible for |
| # stdout. So we end up with \r\n here even though we explicitly write |
| # \n. (We could write \r instead, which doesn't get converted to \r\n, |
| # but that's probably more troublesome for people trying to read the |
| # files.) |
| print(line + '\n', end=' ') |
| |
| # Python on windows writes the buffer only when it reaches 4k. This is |
| # not fast enough for all purposes. |
| sys.stdout.flush() |
| line = read_from.readline() |
| |
| # Make sure the process terminates. |
| proc.wait() |
| |
| if not verbose: |
| out.close() |
| return (proc.returncode, output) |
| |
| def RunCommand(command, verbose=True): |
| """Runs the command list, printing its output and returning its exit status. |
| |
| Prints the given command (which should be a list of one or more strings), |
| then runs it and prints its stderr (and optionally stdout) to stdout, |
| line-buffered, converting line endings to CRLF. Waits for the command to |
| terminate and returns its status. |
| |
| Args: |
| command: the full command to run, as a list of one or more strings |
| verbose: if True, combines all output (stdout and stderr) into stdout. |
| Otherwise, prints only the command's stderr to stdout. |
| |
| Returns: |
| The process's exit status. |
| |
| Raises: |
| CommandNotFound if the command executable could not be found. |
| """ |
| return RunCommandFull(command, verbose)[0] |
| |
| def RunCommandsInParallel(commands, verbose=True, collect_output=False, |
| print_output=True): |
| """Runs a list of commands in parallel, waits for all commands to terminate |
| and returns their status. If specified, the ouput of commands can be |
| returned and/or printed. |
| |
| Args: |
| commands: the list of commands to run, each as a list of one or more |
| strings. |
| verbose: if True, combines stdout and stderr into stdout. |
| Otherwise, prints only the command's stderr to stdout. |
| collect_output: if True, collects the output of the each command as a list |
| of lines and returns it. |
| print_output: if True, prints the output of each command. |
| |
| Returns: |
| A list of tuples consisting of each command's exit status and output. If |
| collect_output is False, the output will be []. |
| |
| Raises: |
| CommandNotFound if any of the command executables could not be found. |
| """ |
| |
| command_num = len(commands) |
| outputs = [[] for i in xrange(command_num)] |
| procs = [None for i in xrange(command_num)] |
| eofs = [False for i in xrange(command_num)] |
| |
| for command in commands: |
| print( |
| '\n' + subprocess.list2cmdline(command).replace('\\', '/') + '\n', |
| end=' ') |
| |
| if verbose: |
| out = subprocess.PIPE |
| err = subprocess.STDOUT |
| else: |
| out = file(os.devnull, 'w') |
| err = subprocess.PIPE |
| |
| for i in xrange(command_num): |
| try: |
| command = commands[i] |
| procs[i] = subprocess.Popen(command, stdout=out, stderr=err, bufsize=1) |
| except OSError, e: |
| if e.errno == errno.ENOENT: |
| raise CommandNotFound('Unable to find "%s"' % command[0]) |
| raise |
| # We could consider terminating the processes already started. |
| # But Popen.kill() is only available in version 2.6. |
| # For now the clean up is done by KillAll. |
| |
| while True: |
| eof_all = True |
| for i in xrange(command_num): |
| if eofs[i]: |
| continue |
| if verbose: |
| read_from = procs[i].stdout |
| else: |
| read_from = procs[i].stderr |
| line = read_from.readline() |
| if line: |
| eof_all = False |
| line = line.rstrip() |
| outputs[i].append(line) |
| if print_output: |
| # Windows Python converts \n to \r\n automatically whenever it |
| # encounters it written to a text file (including stdout). The only |
| # way around it is to write to a binary file, which isn't feasible |
| # for stdout. So we end up with \r\n here even though we explicitly |
| # write \n. (We could write \r instead, which doesn't get converted |
| # to \r\n, but that's probably more troublesome for people trying to |
| # read the files.) |
| print(line + '\n', end=' ') |
| else: |
| eofs[i] = True |
| if eof_all: |
| break |
| |
| # Make sure the process terminates. |
| for i in xrange(command_num): |
| procs[i].wait() |
| |
| if not verbose: |
| out.close() |
| |
| return [(procs[i].returncode, outputs[i]) for i in xrange(command_num)] |