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