blob: 103358420dd2b1a5fe23586fddb38bb683ed6a31 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass26132882012-01-14 15:12:45 +00004
5import os
Simon Glassa997ea52020-04-17 18:09:04 -06006
Simon Glass131444f2023-02-23 18:18:04 -07007from u_boot_pylib import cros_subprocess
Simon Glass26132882012-01-14 15:12:45 +00008
Simon Glass5dc22cf2025-02-03 09:26:42 -07009# This permits interception of RunPipe for test purposes. If it is set to
10# a function, then that function is called with the pipe list being
11# executed. Otherwise, it is assumed to be a CommandResult object, and is
12# returned as the result for every run_pipe() call.
13# When this value is None, commands are executed as normal.
14TEST_RESULT = None
15
Simon Glass26132882012-01-14 15:12:45 +000016"""Shell command ease-ups for Python."""
17
Simon Glass34e59432012-12-15 10:42:04 +000018class CommandResult:
19 """A class which captures the result of executing a command.
20
21 Members:
22 stdout: stdout obtained from command, as a string
23 stderr: stderr obtained from command, as a string
24 return_code: Return code from command
25 exception: Exception received, or None if all ok
26 """
Simon Glassde384972014-09-05 19:00:12 -060027 def __init__(self, stdout='', stderr='', combined='', return_code=0,
28 exception=None):
29 self.stdout = stdout
30 self.stderr = stderr
31 self.combined = combined
32 self.return_code = return_code
33 self.exception = exception
34
Simon Glass840be732022-01-29 14:14:05 -070035 def to_output(self, binary):
Simon Glasscc311ac2019-10-31 07:42:50 -060036 if not binary:
Simon Glass4ba3ea52020-06-07 06:45:46 -060037 self.stdout = self.stdout.decode('utf-8')
38 self.stderr = self.stderr.decode('utf-8')
39 self.combined = self.combined.decode('utf-8')
Simon Glasscc311ac2019-10-31 07:42:50 -060040 return self
41
Simon Glass840be732022-01-29 14:14:05 -070042def run_pipe(pipe_list, infile=None, outfile=None,
Simon Glass34e59432012-12-15 10:42:04 +000043 capture=False, capture_stderr=False, oneline=False,
Simon Glass146b6022021-10-19 21:43:24 -060044 raise_on_error=True, cwd=None, binary=False,
45 output_func=None, **kwargs):
Simon Glass26132882012-01-14 15:12:45 +000046 """
47 Perform a command pipeline, with optional input/output filenames.
48
Simon Glass34e59432012-12-15 10:42:04 +000049 Args:
50 pipe_list: List of command lines to execute. Each command line is
51 piped into the next, and is itself a list of strings. For
52 example [ ['ls', '.git'] ['wc'] ] will pipe the output of
53 'ls .git' into 'wc'.
54 infile: File to provide stdin to the pipeline
55 outfile: File to store stdout
56 capture: True to capture output
57 capture_stderr: True to capture stderr
58 oneline: True to strip newline chars from output
Simon Glass146b6022021-10-19 21:43:24 -060059 output_func: Output function to call with each output fragment
60 (if it returns True the function terminates)
Simon Glass34e59432012-12-15 10:42:04 +000061 kwargs: Additional keyword arguments to cros_subprocess.Popen()
62 Returns:
63 CommandResult object
Simon Glass26132882012-01-14 15:12:45 +000064 """
Simon Glass5dc22cf2025-02-03 09:26:42 -070065 if TEST_RESULT:
66 if hasattr(TEST_RESULT, '__call__'):
Simon Glass547cba62022-02-11 13:23:18 -070067 # pylint: disable=E1102
Simon Glass5dc22cf2025-02-03 09:26:42 -070068 result = TEST_RESULT(pipe_list=pipe_list)
Simon Glass6a8480b2018-07-17 13:25:42 -060069 if result:
70 return result
71 else:
Simon Glass5dc22cf2025-02-03 09:26:42 -070072 return TEST_RESULT
Simon Glass6a8480b2018-07-17 13:25:42 -060073 # No result: fall through to normal processing
Simon Glasscc311ac2019-10-31 07:42:50 -060074 result = CommandResult(b'', b'', b'')
Simon Glass26132882012-01-14 15:12:45 +000075 last_pipe = None
Simon Glass34e59432012-12-15 10:42:04 +000076 pipeline = list(pipe_list)
Simon Glass519fad22012-12-15 10:42:05 +000077 user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list])
Simon Glassf1bf6862014-09-05 19:00:09 -060078 kwargs['stdout'] = None
79 kwargs['stderr'] = None
Simon Glass26132882012-01-14 15:12:45 +000080 while pipeline:
81 cmd = pipeline.pop(0)
Simon Glass26132882012-01-14 15:12:45 +000082 if last_pipe is not None:
83 kwargs['stdin'] = last_pipe.stdout
84 elif infile:
85 kwargs['stdin'] = open(infile, 'rb')
86 if pipeline or capture:
Simon Glass34e59432012-12-15 10:42:04 +000087 kwargs['stdout'] = cros_subprocess.PIPE
Simon Glass26132882012-01-14 15:12:45 +000088 elif outfile:
89 kwargs['stdout'] = open(outfile, 'wb')
Simon Glass34e59432012-12-15 10:42:04 +000090 if capture_stderr:
91 kwargs['stderr'] = cros_subprocess.PIPE
Simon Glass26132882012-01-14 15:12:45 +000092
Simon Glass34e59432012-12-15 10:42:04 +000093 try:
94 last_pipe = cros_subprocess.Popen(cmd, cwd=cwd, **kwargs)
Paul Burtonf14a1312016-09-27 16:03:51 +010095 except Exception as err:
Simon Glass34e59432012-12-15 10:42:04 +000096 result.exception = err
Simon Glass519fad22012-12-15 10:42:05 +000097 if raise_on_error:
98 raise Exception("Error running '%s': %s" % (user_pipestr, str))
99 result.return_code = 255
Simon Glass840be732022-01-29 14:14:05 -0700100 return result.to_output(binary)
Simon Glass26132882012-01-14 15:12:45 +0000101
102 if capture:
Simon Glass34e59432012-12-15 10:42:04 +0000103 result.stdout, result.stderr, result.combined = (
Simon Glass4c0557b2022-01-29 14:14:08 -0700104 last_pipe.communicate_filter(output_func))
Simon Glass34e59432012-12-15 10:42:04 +0000105 if result.stdout and oneline:
Simon Glasscc311ac2019-10-31 07:42:50 -0600106 result.output = result.stdout.rstrip(b'\r\n')
Simon Glass85bbeeb2023-11-01 11:17:50 -0600107 result.return_code = last_pipe.wait()
Simon Glass519fad22012-12-15 10:42:05 +0000108 if raise_on_error and result.return_code:
109 raise Exception("Error running '%s'" % user_pipestr)
Simon Glass840be732022-01-29 14:14:05 -0700110 return result.to_output(binary)
Simon Glass26132882012-01-14 15:12:45 +0000111
Simon Glass840be732022-01-29 14:14:05 -0700112def output(*cmd, **kwargs):
Simon Glass097483d2019-07-08 13:18:23 -0600113 kwargs['raise_on_error'] = kwargs.get('raise_on_error', True)
Simon Glass840be732022-01-29 14:14:05 -0700114 return run_pipe([cmd], capture=True, **kwargs).stdout
Simon Glass26132882012-01-14 15:12:45 +0000115
Simon Glass840be732022-01-29 14:14:05 -0700116def output_one_line(*cmd, **kwargs):
Simon Glasscc311ac2019-10-31 07:42:50 -0600117 """Run a command and output it as a single-line string
118
119 The command us expected to produce a single line of output
120
121 Returns:
122 String containing output of command
123 """
Simon Glass519fad22012-12-15 10:42:05 +0000124 raise_on_error = kwargs.pop('raise_on_error', True)
Simon Glass840be732022-01-29 14:14:05 -0700125 result = run_pipe([cmd], capture=True, oneline=True,
Simon Glasscc311ac2019-10-31 07:42:50 -0600126 raise_on_error=raise_on_error, **kwargs).stdout.strip()
127 return result
Simon Glass26132882012-01-14 15:12:45 +0000128
Simon Glass840be732022-01-29 14:14:05 -0700129def run(*cmd, **kwargs):
130 return run_pipe([cmd], **kwargs).stdout
Simon Glass26132882012-01-14 15:12:45 +0000131
Simon Glass840be732022-01-29 14:14:05 -0700132def run_list(cmd):
133 return run_pipe([cmd], capture=True).stdout
Simon Glass34e59432012-12-15 10:42:04 +0000134
Simon Glass840be732022-01-29 14:14:05 -0700135def stop_all():
Simon Glass34e59432012-12-15 10:42:04 +0000136 cros_subprocess.stay_alive = False