1*4882a593Smuzhiyun# Copyright (c) 2011 The Chromium OS Authors. 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0+ 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunimport os 7*4882a593Smuzhiyunimport cros_subprocess 8*4882a593Smuzhiyun 9*4882a593Smuzhiyun"""Shell command ease-ups for Python.""" 10*4882a593Smuzhiyun 11*4882a593Smuzhiyunclass CommandResult: 12*4882a593Smuzhiyun """A class which captures the result of executing a command. 13*4882a593Smuzhiyun 14*4882a593Smuzhiyun Members: 15*4882a593Smuzhiyun stdout: stdout obtained from command, as a string 16*4882a593Smuzhiyun stderr: stderr obtained from command, as a string 17*4882a593Smuzhiyun return_code: Return code from command 18*4882a593Smuzhiyun exception: Exception received, or None if all ok 19*4882a593Smuzhiyun """ 20*4882a593Smuzhiyun def __init__(self): 21*4882a593Smuzhiyun self.stdout = None 22*4882a593Smuzhiyun self.stderr = None 23*4882a593Smuzhiyun self.combined = None 24*4882a593Smuzhiyun self.return_code = None 25*4882a593Smuzhiyun self.exception = None 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun def __init__(self, stdout='', stderr='', combined='', return_code=0, 28*4882a593Smuzhiyun exception=None): 29*4882a593Smuzhiyun self.stdout = stdout 30*4882a593Smuzhiyun self.stderr = stderr 31*4882a593Smuzhiyun self.combined = combined 32*4882a593Smuzhiyun self.return_code = return_code 33*4882a593Smuzhiyun self.exception = exception 34*4882a593Smuzhiyun 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun# This permits interception of RunPipe for test purposes. If it is set to 37*4882a593Smuzhiyun# a function, then that function is called with the pipe list being 38*4882a593Smuzhiyun# executed. Otherwise, it is assumed to be a CommandResult object, and is 39*4882a593Smuzhiyun# returned as the result for every RunPipe() call. 40*4882a593Smuzhiyun# When this value is None, commands are executed as normal. 41*4882a593Smuzhiyuntest_result = None 42*4882a593Smuzhiyun 43*4882a593Smuzhiyundef RunPipe(pipe_list, infile=None, outfile=None, 44*4882a593Smuzhiyun capture=False, capture_stderr=False, oneline=False, 45*4882a593Smuzhiyun raise_on_error=True, cwd=None, **kwargs): 46*4882a593Smuzhiyun """ 47*4882a593Smuzhiyun Perform a command pipeline, with optional input/output filenames. 48*4882a593Smuzhiyun 49*4882a593Smuzhiyun Args: 50*4882a593Smuzhiyun pipe_list: List of command lines to execute. Each command line is 51*4882a593Smuzhiyun piped into the next, and is itself a list of strings. For 52*4882a593Smuzhiyun example [ ['ls', '.git'] ['wc'] ] will pipe the output of 53*4882a593Smuzhiyun 'ls .git' into 'wc'. 54*4882a593Smuzhiyun infile: File to provide stdin to the pipeline 55*4882a593Smuzhiyun outfile: File to store stdout 56*4882a593Smuzhiyun capture: True to capture output 57*4882a593Smuzhiyun capture_stderr: True to capture stderr 58*4882a593Smuzhiyun oneline: True to strip newline chars from output 59*4882a593Smuzhiyun kwargs: Additional keyword arguments to cros_subprocess.Popen() 60*4882a593Smuzhiyun Returns: 61*4882a593Smuzhiyun CommandResult object 62*4882a593Smuzhiyun """ 63*4882a593Smuzhiyun if test_result: 64*4882a593Smuzhiyun if hasattr(test_result, '__call__'): 65*4882a593Smuzhiyun return test_result(pipe_list=pipe_list) 66*4882a593Smuzhiyun return test_result 67*4882a593Smuzhiyun result = CommandResult() 68*4882a593Smuzhiyun last_pipe = None 69*4882a593Smuzhiyun pipeline = list(pipe_list) 70*4882a593Smuzhiyun user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list]) 71*4882a593Smuzhiyun kwargs['stdout'] = None 72*4882a593Smuzhiyun kwargs['stderr'] = None 73*4882a593Smuzhiyun while pipeline: 74*4882a593Smuzhiyun cmd = pipeline.pop(0) 75*4882a593Smuzhiyun if last_pipe is not None: 76*4882a593Smuzhiyun kwargs['stdin'] = last_pipe.stdout 77*4882a593Smuzhiyun elif infile: 78*4882a593Smuzhiyun kwargs['stdin'] = open(infile, 'rb') 79*4882a593Smuzhiyun if pipeline or capture: 80*4882a593Smuzhiyun kwargs['stdout'] = cros_subprocess.PIPE 81*4882a593Smuzhiyun elif outfile: 82*4882a593Smuzhiyun kwargs['stdout'] = open(outfile, 'wb') 83*4882a593Smuzhiyun if capture_stderr: 84*4882a593Smuzhiyun kwargs['stderr'] = cros_subprocess.PIPE 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun try: 87*4882a593Smuzhiyun last_pipe = cros_subprocess.Popen(cmd, cwd=cwd, **kwargs) 88*4882a593Smuzhiyun except Exception as err: 89*4882a593Smuzhiyun result.exception = err 90*4882a593Smuzhiyun if raise_on_error: 91*4882a593Smuzhiyun raise Exception("Error running '%s': %s" % (user_pipestr, str)) 92*4882a593Smuzhiyun result.return_code = 255 93*4882a593Smuzhiyun return result 94*4882a593Smuzhiyun 95*4882a593Smuzhiyun if capture: 96*4882a593Smuzhiyun result.stdout, result.stderr, result.combined = ( 97*4882a593Smuzhiyun last_pipe.CommunicateFilter(None)) 98*4882a593Smuzhiyun if result.stdout and oneline: 99*4882a593Smuzhiyun result.output = result.stdout.rstrip('\r\n') 100*4882a593Smuzhiyun result.return_code = last_pipe.wait() 101*4882a593Smuzhiyun else: 102*4882a593Smuzhiyun result.return_code = os.waitpid(last_pipe.pid, 0)[1] 103*4882a593Smuzhiyun if raise_on_error and result.return_code: 104*4882a593Smuzhiyun raise Exception("Error running '%s'" % user_pipestr) 105*4882a593Smuzhiyun return result 106*4882a593Smuzhiyun 107*4882a593Smuzhiyundef Output(*cmd, **kwargs): 108*4882a593Smuzhiyun raise_on_error = kwargs.get('raise_on_error', True) 109*4882a593Smuzhiyun return RunPipe([cmd], capture=True, raise_on_error=raise_on_error).stdout 110*4882a593Smuzhiyun 111*4882a593Smuzhiyundef OutputOneLine(*cmd, **kwargs): 112*4882a593Smuzhiyun raise_on_error = kwargs.pop('raise_on_error', True) 113*4882a593Smuzhiyun return (RunPipe([cmd], capture=True, oneline=True, 114*4882a593Smuzhiyun raise_on_error=raise_on_error, 115*4882a593Smuzhiyun **kwargs).stdout.strip()) 116*4882a593Smuzhiyun 117*4882a593Smuzhiyundef Run(*cmd, **kwargs): 118*4882a593Smuzhiyun return RunPipe([cmd], **kwargs).stdout 119*4882a593Smuzhiyun 120*4882a593Smuzhiyundef RunList(cmd): 121*4882a593Smuzhiyun return RunPipe([cmd], capture=True).stdout 122*4882a593Smuzhiyun 123*4882a593Smuzhiyundef StopAll(): 124*4882a593Smuzhiyun cros_subprocess.stay_alive = False 125