Home | History | Annotate | Download | only in libcxx
      1 #===----------------------------------------------------------------------===##
      2 #
      3 #                     The LLVM Compiler Infrastructure
      4 #
      5 # This file is dual licensed under the MIT and the University of Illinois Open
      6 # Source Licenses. See LICENSE.TXT for details.
      7 #
      8 #===----------------------------------------------------------------------===##
      9 
     10 from contextlib import contextmanager
     11 import errno
     12 import os
     13 import platform
     14 import signal
     15 import subprocess
     16 import sys
     17 import tempfile
     18 import threading
     19 
     20 
     21 # FIXME: Most of these functions are cribbed from LIT
     22 def to_bytes(str):
     23     # Encode to UTF-8 to get binary data.
     24     if isinstance(str, bytes):
     25         return str
     26     return str.encode('utf-8')
     27 
     28 def to_string(bytes):
     29     if isinstance(bytes, str):
     30         return bytes
     31     return to_bytes(bytes)
     32 
     33 def convert_string(bytes):
     34     try:
     35         return to_string(bytes.decode('utf-8'))
     36     except AttributeError: # 'str' object has no attribute 'decode'.
     37         return str(bytes)
     38     except UnicodeError:
     39         return str(bytes)
     40 
     41 
     42 def cleanFile(filename):
     43     try:
     44         os.remove(filename)
     45     except OSError:
     46         pass
     47 
     48 
     49 @contextmanager
     50 def guardedTempFilename(suffix='', prefix='', dir=None):
     51     # Creates and yeilds a temporary filename within a with statement. The file
     52     # is removed upon scope exit.
     53     handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir)
     54     os.close(handle)
     55     yield name
     56     cleanFile(name)
     57 
     58 
     59 @contextmanager
     60 def guardedFilename(name):
     61     # yeilds a filename within a with statement. The file is removed upon scope
     62     # exit.
     63     yield name
     64     cleanFile(name)
     65 
     66 
     67 @contextmanager
     68 def nullContext(value):
     69     # yeilds a variable within a with statement. No action is taken upon scope
     70     # exit.
     71     yield value
     72 
     73 
     74 def makeReport(cmd, out, err, rc):
     75     report = "Command: %s\n" % cmd
     76     report += "Exit Code: %d\n" % rc
     77     if out:
     78         report += "Standard Output:\n--\n%s--\n" % out
     79     if err:
     80         report += "Standard Error:\n--\n%s--\n" % err
     81     report += '\n'
     82     return report
     83 
     84 
     85 def capture(args, env=None):
     86     """capture(command) - Run the given command (or argv list) in a shell and
     87     return the standard output. Raises a CalledProcessError if the command
     88     exits with a non-zero status."""
     89     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
     90                          env=env)
     91     out, err = p.communicate()
     92     out = convert_string(out)
     93     err = convert_string(err)
     94     if p.returncode != 0:
     95         raise subprocess.CalledProcessError(cmd=args,
     96                                             returncode=p.returncode,
     97                                             output="{}\n{}".format(out, err))
     98     return out
     99 
    100 
    101 def which(command, paths = None):
    102     """which(command, [paths]) - Look up the given command in the paths string
    103     (or the PATH environment variable, if unspecified)."""
    104 
    105     if paths is None:
    106         paths = os.environ.get('PATH','')
    107 
    108     # Check for absolute match first.
    109     if os.path.isfile(command):
    110         return command
    111 
    112     # Would be nice if Python had a lib function for this.
    113     if not paths:
    114         paths = os.defpath
    115 
    116     # Get suffixes to search.
    117     # On Cygwin, 'PATHEXT' may exist but it should not be used.
    118     if os.pathsep == ';':
    119         pathext = os.environ.get('PATHEXT', '').split(';')
    120     else:
    121         pathext = ['']
    122 
    123     # Search the paths...
    124     for path in paths.split(os.pathsep):
    125         for ext in pathext:
    126             p = os.path.join(path, command + ext)
    127             if os.path.exists(p) and not os.path.isdir(p):
    128                 return p
    129 
    130     return None
    131 
    132 
    133 def checkToolsPath(dir, tools):
    134     for tool in tools:
    135         if not os.path.exists(os.path.join(dir, tool)):
    136             return False
    137     return True
    138 
    139 
    140 def whichTools(tools, paths):
    141     for path in paths.split(os.pathsep):
    142         if checkToolsPath(path, tools):
    143             return path
    144     return None
    145 
    146 def mkdir_p(path):
    147     """mkdir_p(path) - Make the "path" directory, if it does not exist; this
    148     will also make directories for any missing parent directories."""
    149     if not path or os.path.exists(path):
    150         return
    151 
    152     parent = os.path.dirname(path)
    153     if parent != path:
    154         mkdir_p(parent)
    155 
    156     try:
    157         os.mkdir(path)
    158     except OSError:
    159         e = sys.exc_info()[1]
    160         # Ignore EEXIST, which may occur during a race condition.
    161         if e.errno != errno.EEXIST:
    162             raise
    163 
    164 
    165 class ExecuteCommandTimeoutException(Exception):
    166     def __init__(self, msg, out, err, exitCode):
    167         assert isinstance(msg, str)
    168         assert isinstance(out, str)
    169         assert isinstance(err, str)
    170         assert isinstance(exitCode, int)
    171         self.msg = msg
    172         self.out = out
    173         self.err = err
    174         self.exitCode = exitCode
    175 
    176 # Close extra file handles on UNIX (on Windows this cannot be done while
    177 # also redirecting input).
    178 kUseCloseFDs = not (platform.system() == 'Windows')
    179 def executeCommand(command, cwd=None, env=None, input=None, timeout=0):
    180     """
    181         Execute command ``command`` (list of arguments or string)
    182         with
    183         * working directory ``cwd`` (str), use None to use the current
    184           working directory
    185         * environment ``env`` (dict), use None for none
    186         * Input to the command ``input`` (str), use string to pass
    187           no input.
    188         * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout.
    189 
    190         Returns a tuple (out, err, exitCode) where
    191         * ``out`` (str) is the standard output of running the command
    192         * ``err`` (str) is the standard error of running the command
    193         * ``exitCode`` (int) is the exitCode of running the command
    194 
    195         If the timeout is hit an ``ExecuteCommandTimeoutException``
    196         is raised.
    197     """
    198     if input is not None:
    199         input = to_bytes(input)
    200     p = subprocess.Popen(command, cwd=cwd,
    201                          stdin=subprocess.PIPE,
    202                          stdout=subprocess.PIPE,
    203                          stderr=subprocess.PIPE,
    204                          env=env, close_fds=kUseCloseFDs)
    205     timerObject = None
    206     # FIXME: Because of the way nested function scopes work in Python 2.x we
    207     # need to use a reference to a mutable object rather than a plain
    208     # bool. In Python 3 we could use the "nonlocal" keyword but we need
    209     # to support Python 2 as well.
    210     hitTimeOut = [False]
    211     try:
    212         if timeout > 0:
    213             def killProcess():
    214                 # We may be invoking a shell so we need to kill the
    215                 # process and all its children.
    216                 hitTimeOut[0] = True
    217                 killProcessAndChildren(p.pid)
    218 
    219             timerObject = threading.Timer(timeout, killProcess)
    220             timerObject.start()
    221 
    222         out,err = p.communicate(input=input)
    223         exitCode = p.wait()
    224     finally:
    225         if timerObject != None:
    226             timerObject.cancel()
    227 
    228     # Ensure the resulting output is always of string type.
    229     out = convert_string(out)
    230     err = convert_string(err)
    231 
    232     if hitTimeOut[0]:
    233         raise ExecuteCommandTimeoutException(
    234             msg='Reached timeout of {} seconds'.format(timeout),
    235             out=out,
    236             err=err,
    237             exitCode=exitCode
    238             )
    239 
    240     # Detect Ctrl-C in subprocess.
    241     if exitCode == -signal.SIGINT:
    242         raise KeyboardInterrupt
    243 
    244     return out, err, exitCode
    245 
    246 
    247 def killProcessAndChildren(pid):
    248     """
    249     This function kills a process with ``pid`` and all its
    250     running children (recursively). It is currently implemented
    251     using the psutil module which provides a simple platform
    252     neutral implementation.
    253 
    254     TODO: Reimplement this without using psutil so we can
    255           remove our dependency on it.
    256     """
    257     import psutil
    258     try:
    259         psutilProc = psutil.Process(pid)
    260         # Handle the different psutil API versions
    261         try:
    262             # psutil >= 2.x
    263             children_iterator = psutilProc.children(recursive=True)
    264         except AttributeError:
    265             # psutil 1.x
    266             children_iterator = psutilProc.get_children(recursive=True)
    267         for child in children_iterator:
    268             try:
    269                 child.kill()
    270             except psutil.NoSuchProcess:
    271                 pass
    272         psutilProc.kill()
    273     except psutil.NoSuchProcess:
    274         pass
    275 
    276 
    277 def executeCommandVerbose(cmd, *args, **kwargs):
    278     """
    279     Execute a command and print its output on failure.
    280     """
    281     out, err, exitCode = executeCommand(cmd, *args, **kwargs)
    282     if exitCode != 0:
    283         report = makeReport(cmd, out, err, exitCode)
    284         report += "\n\nFailed!"
    285         sys.stderr.write('%s\n' % report)
    286     return out, err, exitCode
    287