222 lines
7.3 KiB
Python
222 lines
7.3 KiB
Python
# 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)]
|