blob: a98dcedd3220c505a0764d93c093c92c1756452c [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass96273882025-02-03 09:26:44 -07002"""
3Shell command ease-ups for Python
Simon Glass26132882012-01-14 15:12:45 +00004
Simon Glass96273882025-02-03 09:26:44 -07005Copyright (c) 2011 The Chromium OS Authors.
6"""
7
8import subprocess
Simon Glassa997ea52020-04-17 18:09:04 -06009
Simon Glass131444f2023-02-23 18:18:04 -070010from u_boot_pylib import cros_subprocess
Simon Glass26132882012-01-14 15:12:45 +000011
Simon Glass5dc22cf2025-02-03 09:26:42 -070012# This permits interception of RunPipe for test purposes. If it is set to
13# a function, then that function is called with the pipe list being
14# executed. Otherwise, it is assumed to be a CommandResult object, and is
15# returned as the result for every run_pipe() call.
16# When this value is None, commands are executed as normal.
17TEST_RESULT = None
18
Simon Glass82327692025-02-03 09:26:43 -070019
20class CommandExc(Exception):
21 """Reports an exception to the caller"""
22 def __init__(self, msg, result):
23 """Set up a new exception object
24
25 Args:
26 result (CommandResult): Execution result so far
27 """
28 super().__init__(msg)
29 self.result = result
30
Simon Glass26132882012-01-14 15:12:45 +000031
Simon Glass34e59432012-12-15 10:42:04 +000032class CommandResult:
33 """A class which captures the result of executing a command.
34
35 Members:
Simon Glass96273882025-02-03 09:26:44 -070036 stdout (bytes): stdout obtained from command, as a string
37 stderr (bytes): stderr obtained from command, as a string
38 combined (bytes): stdout and stderr interleaved
39 return_code (int): Return code from command
40 exception (Exception): Exception received, or None if all ok
41 output (str or None): Returns output as a single line if requested
Simon Glass34e59432012-12-15 10:42:04 +000042 """
Simon Glassde384972014-09-05 19:00:12 -060043 def __init__(self, stdout='', stderr='', combined='', return_code=0,
44 exception=None):
45 self.stdout = stdout
46 self.stderr = stderr
47 self.combined = combined
48 self.return_code = return_code
49 self.exception = exception
Simon Glass96273882025-02-03 09:26:44 -070050 self.output = None
Simon Glassde384972014-09-05 19:00:12 -060051
Simon Glass840be732022-01-29 14:14:05 -070052 def to_output(self, binary):
Simon Glass96273882025-02-03 09:26:44 -070053 """Converts binary output to its final form
54
55 Args:
56 binary (bool): True to report binary output, False to use strings
57 Returns:
58 self
59 """
Simon Glasscc311ac2019-10-31 07:42:50 -060060 if not binary:
Simon Glass4ba3ea52020-06-07 06:45:46 -060061 self.stdout = self.stdout.decode('utf-8')
62 self.stderr = self.stderr.decode('utf-8')
63 self.combined = self.combined.decode('utf-8')
Simon Glasscc311ac2019-10-31 07:42:50 -060064 return self
65
Simon Glass96273882025-02-03 09:26:44 -070066
67def run_pipe(pipe_list, infile=None, outfile=None, capture=False,
68 capture_stderr=False, oneline=False, raise_on_error=True, cwd=None,
69 binary=False, output_func=None, **kwargs):
Simon Glass26132882012-01-14 15:12:45 +000070 """
71 Perform a command pipeline, with optional input/output filenames.
72
Simon Glass34e59432012-12-15 10:42:04 +000073 Args:
Simon Glass96273882025-02-03 09:26:44 -070074 pipe_list (list of list): List of command lines to execute. Each command
75 line is piped into the next, and is itself a list of strings. For
Simon Glass34e59432012-12-15 10:42:04 +000076 example [ ['ls', '.git'] ['wc'] ] will pipe the output of
77 'ls .git' into 'wc'.
Simon Glass96273882025-02-03 09:26:44 -070078 infile (str): File to provide stdin to the pipeline
79 outfile (str): File to store stdout
80 capture (bool): True to capture output
81 capture_stderr (bool): True to capture stderr
82 oneline (bool): True to strip newline chars from output
83 raise_on_error (bool): True to raise on an error, False to return it in
84 the CommandResult
85 cwd (str or None): Directory to run the command in
86 binary (bool): True to report binary output, False to use strings
87 output_func (function): Output function to call with each output
88 fragment (if it returns True the function terminates)
89 **kwargs: Additional keyword arguments to cros_subprocess.Popen()
Simon Glass34e59432012-12-15 10:42:04 +000090 Returns:
91 CommandResult object
Simon Glass82327692025-02-03 09:26:43 -070092 Raises:
93 CommandExc if an exception happens
Simon Glass26132882012-01-14 15:12:45 +000094 """
Simon Glass5dc22cf2025-02-03 09:26:42 -070095 if TEST_RESULT:
96 if hasattr(TEST_RESULT, '__call__'):
Simon Glass547cba62022-02-11 13:23:18 -070097 # pylint: disable=E1102
Simon Glass5dc22cf2025-02-03 09:26:42 -070098 result = TEST_RESULT(pipe_list=pipe_list)
Simon Glass6a8480b2018-07-17 13:25:42 -060099 if result:
100 return result
101 else:
Simon Glass5dc22cf2025-02-03 09:26:42 -0700102 return TEST_RESULT
Simon Glass6a8480b2018-07-17 13:25:42 -0600103 # No result: fall through to normal processing
Simon Glasscc311ac2019-10-31 07:42:50 -0600104 result = CommandResult(b'', b'', b'')
Simon Glass26132882012-01-14 15:12:45 +0000105 last_pipe = None
Simon Glass34e59432012-12-15 10:42:04 +0000106 pipeline = list(pipe_list)
Simon Glass96273882025-02-03 09:26:44 -0700107 user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list])
Simon Glassf1bf6862014-09-05 19:00:09 -0600108 kwargs['stdout'] = None
109 kwargs['stderr'] = None
Simon Glass26132882012-01-14 15:12:45 +0000110 while pipeline:
111 cmd = pipeline.pop(0)
Simon Glass26132882012-01-14 15:12:45 +0000112 if last_pipe is not None:
113 kwargs['stdin'] = last_pipe.stdout
114 elif infile:
115 kwargs['stdin'] = open(infile, 'rb')
116 if pipeline or capture:
Simon Glass34e59432012-12-15 10:42:04 +0000117 kwargs['stdout'] = cros_subprocess.PIPE
Simon Glass26132882012-01-14 15:12:45 +0000118 elif outfile:
119 kwargs['stdout'] = open(outfile, 'wb')
Simon Glass34e59432012-12-15 10:42:04 +0000120 if capture_stderr:
121 kwargs['stderr'] = cros_subprocess.PIPE
Simon Glass26132882012-01-14 15:12:45 +0000122
Simon Glass34e59432012-12-15 10:42:04 +0000123 try:
124 last_pipe = cros_subprocess.Popen(cmd, cwd=cwd, **kwargs)
Paul Burtonf14a1312016-09-27 16:03:51 +0100125 except Exception as err:
Simon Glass34e59432012-12-15 10:42:04 +0000126 result.exception = err
Simon Glass519fad22012-12-15 10:42:05 +0000127 if raise_on_error:
Simon Glass82327692025-02-03 09:26:43 -0700128 raise CommandExc(f"Error running '{user_pipestr}': {err}",
129 result) from err
Simon Glass519fad22012-12-15 10:42:05 +0000130 result.return_code = 255
Simon Glass840be732022-01-29 14:14:05 -0700131 return result.to_output(binary)
Simon Glass26132882012-01-14 15:12:45 +0000132
133 if capture:
Simon Glass34e59432012-12-15 10:42:04 +0000134 result.stdout, result.stderr, result.combined = (
Simon Glass4c0557b2022-01-29 14:14:08 -0700135 last_pipe.communicate_filter(output_func))
Simon Glass34e59432012-12-15 10:42:04 +0000136 if result.stdout and oneline:
Simon Glasscc311ac2019-10-31 07:42:50 -0600137 result.output = result.stdout.rstrip(b'\r\n')
Simon Glass85bbeeb2023-11-01 11:17:50 -0600138 result.return_code = last_pipe.wait()
Simon Glass519fad22012-12-15 10:42:05 +0000139 if raise_on_error and result.return_code:
Simon Glass82327692025-02-03 09:26:43 -0700140 raise CommandExc(f"Error running '{user_pipestr}'", result)
Simon Glass840be732022-01-29 14:14:05 -0700141 return result.to_output(binary)
Simon Glass26132882012-01-14 15:12:45 +0000142
Simon Glass96273882025-02-03 09:26:44 -0700143
Simon Glass840be732022-01-29 14:14:05 -0700144def output(*cmd, **kwargs):
Simon Glass96273882025-02-03 09:26:44 -0700145 """Run a command and return its output
146
147 Args:
148 *cmd (list of str): Command to run
149 **kwargs (dict of args): Extra arguments to pass in
150
151 Returns:
152 str: command output
153 """
Simon Glass097483d2019-07-08 13:18:23 -0600154 kwargs['raise_on_error'] = kwargs.get('raise_on_error', True)
Simon Glass840be732022-01-29 14:14:05 -0700155 return run_pipe([cmd], capture=True, **kwargs).stdout
Simon Glass26132882012-01-14 15:12:45 +0000156
Simon Glass96273882025-02-03 09:26:44 -0700157
Simon Glass840be732022-01-29 14:14:05 -0700158def output_one_line(*cmd, **kwargs):
Simon Glasscc311ac2019-10-31 07:42:50 -0600159 """Run a command and output it as a single-line string
160
Simon Glass96273882025-02-03 09:26:44 -0700161 The command is expected to produce a single line of output
162
163 Args:
164 *cmd (list of str): Command to run
165 **kwargs (dict of args): Extra arguments to pass in
Simon Glasscc311ac2019-10-31 07:42:50 -0600166
167 Returns:
Simon Glass96273882025-02-03 09:26:44 -0700168 str: output of command with all newlines removed
Simon Glasscc311ac2019-10-31 07:42:50 -0600169 """
Simon Glass519fad22012-12-15 10:42:05 +0000170 raise_on_error = kwargs.pop('raise_on_error', True)
Simon Glass840be732022-01-29 14:14:05 -0700171 result = run_pipe([cmd], capture=True, oneline=True,
Simon Glass96273882025-02-03 09:26:44 -0700172 raise_on_error=raise_on_error, **kwargs).stdout.strip()
Simon Glasscc311ac2019-10-31 07:42:50 -0600173 return result
Simon Glass26132882012-01-14 15:12:45 +0000174
Simon Glass96273882025-02-03 09:26:44 -0700175
Simon Glass840be732022-01-29 14:14:05 -0700176def run(*cmd, **kwargs):
Simon Glass96273882025-02-03 09:26:44 -0700177 """Run a command
178
179 Note that you must add 'capture' to kwargs to obtain non-empty output
180
181 Args:
182 *cmd (list of str): Command to run
183 **kwargs (dict of args): Extra arguments to pass in
184
185 Returns:
186 str: output of command
187 """
Simon Glass840be732022-01-29 14:14:05 -0700188 return run_pipe([cmd], **kwargs).stdout
Simon Glass26132882012-01-14 15:12:45 +0000189
Simon Glass96273882025-02-03 09:26:44 -0700190
Simon Glass840be732022-01-29 14:14:05 -0700191def run_list(cmd):
Simon Glass96273882025-02-03 09:26:44 -0700192 """Run a command and return its output
193
194 Args:
195 cmd (list of str): Command to run
196
197 Returns:
198 str: output of command
199 """
Simon Glass840be732022-01-29 14:14:05 -0700200 return run_pipe([cmd], capture=True).stdout
Simon Glass34e59432012-12-15 10:42:04 +0000201
Simon Glass96273882025-02-03 09:26:44 -0700202
Simon Glass840be732022-01-29 14:14:05 -0700203def stop_all():
Simon Glass96273882025-02-03 09:26:44 -0700204 """Stop all subprocesses initiated with cros_subprocess"""
Simon Glass34e59432012-12-15 10:42:04 +0000205 cros_subprocess.stay_alive = False