diff options
Diffstat (limited to 'site_scons/site_tools/command_output.py')
-rw-r--r-- | site_scons/site_tools/command_output.py | 234 |
1 files changed, 0 insertions, 234 deletions
diff --git a/site_scons/site_tools/command_output.py b/site_scons/site_tools/command_output.py deleted file mode 100644 index 795f3eb..0000000 --- a/site_scons/site_tools/command_output.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/python2.4 -# Copyright 2008, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Command output builder for SCons.""" - - -import os -import signal -import subprocess -import sys -import threading -import time -import SCons.Script - - -# TODO(rspangler): Move KillProcessTree() and RunCommand() into their own -# module. - - -def KillProcessTree(pid): - """Kills the process and all of its child processes. - - Args: - pid: process to kill. - - Raises: - OSError: Unsupported OS. - """ - - if sys.platform in ('win32', 'cygwin'): - # Use Windows' taskkill utility - killproc_path = '%s;%s\\system32;%s\\system32\\wbem' % ( - (os.environ['SYSTEMROOT'],) * 3) - killproc_cmd = 'taskkill /F /T /PID %d' % pid - killproc_task = subprocess.Popen(killproc_cmd, shell=True, - stdout=subprocess.PIPE, - env={'PATH':killproc_path}) - killproc_task.communicate() - - elif sys.platform in ('linux', 'linux2', 'darwin'): - # Use ps to get a list of processes - ps_task = subprocess.Popen(['/bin/ps', 'x', '-o', 'pid,ppid'], stdout=subprocess.PIPE) - ps_out = ps_task.communicate()[0] - - # Parse out a dict of pid->ppid - ppid = {} - for ps_line in ps_out.split('\n'): - w = ps_line.strip().split() - if len(w) < 2: - continue # Not enough words in this line to be a process list - try: - ppid[int(w[0])] = int(w[1]) - except ValueError: - pass # Header or footer - - # For each process, kill it if it or any of its parents is our child - for p in ppid: - p2 = p - while p2: - if p2 == pid: - os.kill(p, signal.SIGKILL) - break - p2 = ppid.get(p2) - - else: - raise OSError('Unsupported OS for KillProcessTree()') - - -def RunCommand(cmdargs, cwdir=None, env=None, echo_output=True, timeout=None, - timeout_errorlevel=14): - """Runs an external command. - - Args: - cmdargs: A command string, or a tuple containing the command and its - arguments. - cwdir: Working directory for the command, if not None. - env: Environment variables dict, if not None. - echo_output: If True, output will be echoed to stdout. - timeout: If not None, timeout for command in seconds. If command times - out, it will be killed and timeout_errorlevel will be returned. - timeout_errorlevel: The value to return if the command times out. - - Returns: - The integer errorlevel from the command. - The combined stdout and stderr as a string. - """ - # Force unicode string in the environment to strings. - if env: - env = dict([(k, str(v)) for k, v in env.items()]) - start_time = time.time() - child = subprocess.Popen(cmdargs, cwd=cwdir, env=env, shell=True, - universal_newlines=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - child_out = [] - child_retcode = None - - def _ReadThread(): - """Thread worker function to read output from child process. - - Necessary since there is no cross-platform way of doing non-blocking - reads of the output pipe. - """ - read_run = True - while read_run: - # Need to have a delay of 1 cycle between child completing and - # thread exit, to pick up the final output from the child. - if child_retcode is not None: - read_run = False - new_out = child.stdout.read() - if new_out: - if echo_output: - print new_out, - child_out.append(new_out) - - read_thread = threading.Thread(target=_ReadThread) - read_thread.start() - - # Wait for child to exit or timeout - while child_retcode is None: - time.sleep(1) # So we don't poll too frequently - child_retcode = child.poll() - if timeout and child_retcode is None: - elapsed = time.time() - start_time - if elapsed > timeout: - print '*** RunCommand() timeout:', cmdargs - KillProcessTree(child.pid) - child_retcode = timeout_errorlevel - - # Wait for worker thread to pick up final output and die - read_thread.join(5) - if read_thread.isAlive(): - print '*** Error: RunCommand() read thread did not exit.' - sys.exit(1) - - if echo_output: - print # end last line of output - return child_retcode, ''.join(child_out) - - -def CommandOutputBuilder(target, source, env): - """Command output builder. - - Args: - self: Environment in which to build - target: List of target nodes - source: List of source nodes - - Returns: - None or 0 if successful; nonzero to indicate failure. - - Runs the command specified in the COMMAND_OUTPUT_CMDLINE environment variable - and stores its output in the first target file. Additional target files - should be specified if the command creates additional output files. - - Runs the command in the COMMAND_OUTPUT_RUN_DIR subdirectory. - """ - env = env.Clone() - - cmdline = env.subst('$COMMAND_OUTPUT_CMDLINE', target=target, source=source) - cwdir = env.subst('$COMMAND_OUTPUT_RUN_DIR', target=target, source=source) - if cwdir: - cwdir = os.path.normpath(cwdir) - env.AppendENVPath('PATH', cwdir) - env.AppendENVPath('LD_LIBRARY_PATH', cwdir) - else: - cwdir = None - cmdecho = env.get('COMMAND_OUTPUT_ECHO', True) - timeout = env.get('COMMAND_OUTPUT_TIMEOUT') - timeout_errorlevel = env.get('COMMAND_OUTPUT_TIMEOUT_ERRORLEVEL') - - retcode, output = RunCommand(cmdline, cwdir=cwdir, env=env['ENV'], - echo_output=cmdecho, timeout=timeout, - timeout_errorlevel=timeout_errorlevel) - - # Save command line output - output_file = open(str(target[0]), 'w') - output_file.write(output) - output_file.close() - - return retcode - - -def generate(env): - # NOTE: SCons requires the use of this name, which fails gpylint. - """SCons entry point for this tool.""" - - # Add the builder and tell it which build environment variables we use. - action = SCons.Script.Action( - CommandOutputBuilder, - 'Output "$COMMAND_OUTPUT_CMDLINE" to $TARGET', - varlist=[ - 'COMMAND_OUTPUT_CMDLINE', - 'COMMAND_OUTPUT_RUN_DIR', - 'COMMAND_OUTPUT_TIMEOUT', - 'COMMAND_OUTPUT_TIMEOUT_ERRORLEVEL', - # We use COMMAND_OUTPUT_ECHO also, but that doesn't change the - # command being run or its output. - ], ) - builder = SCons.Script.Builder(action = action) - env.Append(BUILDERS={'CommandOutput': builder}) - - # Default command line is to run the first input - env['COMMAND_OUTPUT_CMDLINE'] = '$SOURCE' - - # TODO(rspangler): add a pseudo-builder which takes an additional command - # line as an argument. |