# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. # 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.""" 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', ### 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', # 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', 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', 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)]