diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2017-04-16 16:03:23 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2017-04-16 16:03:23 +0000 | 
| commit | 0dc0969cd0a732760f0aa79942a04e0eaef297c4 (patch) | |
| tree | 051bdb57b1ac6ee143f61ddbb47bd0da619f6f0c /utils/libcxx/util.py | |
| parent | 868847c6900e575417c03bced6e562b3af891318 (diff) | |
Notes
Diffstat (limited to 'utils/libcxx/util.py')
| -rw-r--r-- | utils/libcxx/util.py | 286 | 
1 files changed, 286 insertions, 0 deletions
diff --git a/utils/libcxx/util.py b/utils/libcxx/util.py new file mode 100644 index 000000000000..ecfb9afb73b0 --- /dev/null +++ b/utils/libcxx/util.py @@ -0,0 +1,286 @@ +#===----------------------------------------------------------------------===## +# +#                     The LLVM Compiler Infrastructure +# +# This file is dual licensed under the MIT and the University of Illinois Open +# Source Licenses. See LICENSE.TXT for details. +# +#===----------------------------------------------------------------------===## + +from contextlib import contextmanager +import errno +import os +import platform +import signal +import subprocess +import sys +import tempfile +import threading + + +# FIXME: Most of these functions are cribbed from LIT +def to_bytes(str): +    # Encode to UTF-8 to get binary data. +    if isinstance(str, bytes): +        return str +    return str.encode('utf-8') + +def to_string(bytes): +    if isinstance(bytes, str): +        return bytes +    return to_bytes(bytes) + +def convert_string(bytes): +    try: +        return to_string(bytes.decode('utf-8')) +    except AttributeError: # 'str' object has no attribute 'decode'. +        return str(bytes) +    except UnicodeError: +        return str(bytes) + + +def cleanFile(filename): +    try: +        os.remove(filename) +    except OSError: +        pass + + +@contextmanager +def guardedTempFilename(suffix='', prefix='', dir=None): +    # Creates and yeilds a temporary filename within a with statement. The file +    # is removed upon scope exit. +    handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) +    os.close(handle) +    yield name +    cleanFile(name) + + +@contextmanager +def guardedFilename(name): +    # yeilds a filename within a with statement. The file is removed upon scope +    # exit. +    yield name +    cleanFile(name) + + +@contextmanager +def nullContext(value): +    # yeilds a variable within a with statement. No action is taken upon scope +    # exit. +    yield value + + +def makeReport(cmd, out, err, rc): +    report = "Command: %s\n" % cmd +    report += "Exit Code: %d\n" % rc +    if out: +        report += "Standard Output:\n--\n%s--\n" % out +    if err: +        report += "Standard Error:\n--\n%s--\n" % err +    report += '\n' +    return report + + +def capture(args, env=None): +    """capture(command) - Run the given command (or argv list) in a shell and +    return the standard output. Raises a CalledProcessError if the command +    exits with a non-zero status.""" +    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, +                         env=env) +    out, err = p.communicate() +    out = convert_string(out) +    err = convert_string(err) +    if p.returncode != 0: +        raise subprocess.CalledProcessError(cmd=args, +                                            returncode=p.returncode, +                                            output="{}\n{}".format(out, err)) +    return out + + +def which(command, paths = None): +    """which(command, [paths]) - Look up the given command in the paths string +    (or the PATH environment variable, if unspecified).""" + +    if paths is None: +        paths = os.environ.get('PATH','') + +    # Check for absolute match first. +    if os.path.isfile(command): +        return command + +    # Would be nice if Python had a lib function for this. +    if not paths: +        paths = os.defpath + +    # Get suffixes to search. +    # On Cygwin, 'PATHEXT' may exist but it should not be used. +    if os.pathsep == ';': +        pathext = os.environ.get('PATHEXT', '').split(';') +    else: +        pathext = [''] + +    # Search the paths... +    for path in paths.split(os.pathsep): +        for ext in pathext: +            p = os.path.join(path, command + ext) +            if os.path.exists(p) and not os.path.isdir(p): +                return p + +    return None + + +def checkToolsPath(dir, tools): +    for tool in tools: +        if not os.path.exists(os.path.join(dir, tool)): +            return False +    return True + + +def whichTools(tools, paths): +    for path in paths.split(os.pathsep): +        if checkToolsPath(path, tools): +            return path +    return None + +def mkdir_p(path): +    """mkdir_p(path) - Make the "path" directory, if it does not exist; this +    will also make directories for any missing parent directories.""" +    if not path or os.path.exists(path): +        return + +    parent = os.path.dirname(path) +    if parent != path: +        mkdir_p(parent) + +    try: +        os.mkdir(path) +    except OSError: +        e = sys.exc_info()[1] +        # Ignore EEXIST, which may occur during a race condition. +        if e.errno != errno.EEXIST: +            raise + + +class ExecuteCommandTimeoutException(Exception): +    def __init__(self, msg, out, err, exitCode): +        assert isinstance(msg, str) +        assert isinstance(out, str) +        assert isinstance(err, str) +        assert isinstance(exitCode, int) +        self.msg = msg +        self.out = out +        self.err = err +        self.exitCode = exitCode + +# Close extra file handles on UNIX (on Windows this cannot be done while +# also redirecting input). +kUseCloseFDs = not (platform.system() == 'Windows') +def executeCommand(command, cwd=None, env=None, input=None, timeout=0): +    """ +        Execute command ``command`` (list of arguments or string) +        with +        * working directory ``cwd`` (str), use None to use the current +          working directory +        * environment ``env`` (dict), use None for none +        * Input to the command ``input`` (str), use string to pass +          no input. +        * Max execution time ``timeout`` (int) seconds. Use 0 for no timeout. + +        Returns a tuple (out, err, exitCode) where +        * ``out`` (str) is the standard output of running the command +        * ``err`` (str) is the standard error of running the command +        * ``exitCode`` (int) is the exitCode of running the command + +        If the timeout is hit an ``ExecuteCommandTimeoutException`` +        is raised. +    """ +    if input is not None: +        input = to_bytes(input) +    p = subprocess.Popen(command, cwd=cwd, +                         stdin=subprocess.PIPE, +                         stdout=subprocess.PIPE, +                         stderr=subprocess.PIPE, +                         env=env, close_fds=kUseCloseFDs) +    timerObject = None +    # FIXME: Because of the way nested function scopes work in Python 2.x we +    # need to use a reference to a mutable object rather than a plain +    # bool. In Python 3 we could use the "nonlocal" keyword but we need +    # to support Python 2 as well. +    hitTimeOut = [False] +    try: +        if timeout > 0: +            def killProcess(): +                # We may be invoking a shell so we need to kill the +                # process and all its children. +                hitTimeOut[0] = True +                killProcessAndChildren(p.pid) + +            timerObject = threading.Timer(timeout, killProcess) +            timerObject.start() + +        out,err = p.communicate(input=input) +        exitCode = p.wait() +    finally: +        if timerObject != None: +            timerObject.cancel() + +    # Ensure the resulting output is always of string type. +    out = convert_string(out) +    err = convert_string(err) + +    if hitTimeOut[0]: +        raise ExecuteCommandTimeoutException( +            msg='Reached timeout of {} seconds'.format(timeout), +            out=out, +            err=err, +            exitCode=exitCode +            ) + +    # Detect Ctrl-C in subprocess. +    if exitCode == -signal.SIGINT: +        raise KeyboardInterrupt + +    return out, err, exitCode + + +def killProcessAndChildren(pid): +    """ +    This function kills a process with ``pid`` and all its +    running children (recursively). It is currently implemented +    using the psutil module which provides a simple platform +    neutral implementation. + +    TODO: Reimplement this without using psutil so we can +          remove our dependency on it. +    """ +    import psutil +    try: +        psutilProc = psutil.Process(pid) +        # Handle the different psutil API versions +        try: +            # psutil >= 2.x +            children_iterator = psutilProc.children(recursive=True) +        except AttributeError: +            # psutil 1.x +            children_iterator = psutilProc.get_children(recursive=True) +        for child in children_iterator: +            try: +                child.kill() +            except psutil.NoSuchProcess: +                pass +        psutilProc.kill() +    except psutil.NoSuchProcess: +        pass + + +def executeCommandVerbose(cmd, *args, **kwargs): +    """ +    Execute a command and print its output on failure. +    """ +    out, err, exitCode = executeCommand(cmd, *args, **kwargs) +    if exitCode != 0: +        report = makeReport(cmd, out, err, exitCode) +        report += "\n\nFailed!" +        sys.stderr.write('%s\n' % report) +    return out, err, exitCode  | 
