blob: 4a9916bd8149b9799759894b6f009578ef19327b [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 Glass82327692025-02-03 09:26:43 -070016
17class CommandExc(Exception):
18 """Reports an exception to the caller"""
19 def __init__(self, msg, result):
20 """Set up a new exception object
21
22 Args:
23 result (CommandResult): Execution result so far
24 """
25 super().__init__(msg)
26 self.result = result
27
28
Simon Glass26132882012-01-14 15:12:45 +000029"""Shell command ease-ups for Python."""
30
Simon Glass34e59432012-12-15 10:42:04 +000031class CommandResult:
32 """A class which captures the result of executing a command.
33
34 Members:
35 stdout: stdout obtained from command, as a string
36 stderr: stderr obtained from command, as a string
37 return_code: Return code from command
38 exception: Exception received, or None if all ok
39 """
Simon Glassde384972014-09-05 19:00:12 -060040 def __init__(self, stdout='', stderr='', combined='', return_code=0,
41 exception=None):
42 self.stdout = stdout
43 self.stderr = stderr
44 self.combined = combined
45 self.return_code = return_code
46 self.exception = exception
47
Simon Glass840be732022-01-29 14:14:05 -070048 def to_output(self, binary):
Simon Glasscc311ac2019-10-31 07:42:50 -060049 if not binary:
Simon Glass4ba3ea52020-06-07 06:45:46 -060050 self.stdout = self.stdout.decode('utf-8')
51 self.stderr = self.stderr.decode('utf-8')
52 self.combined = self.combined.decode('utf-8')
Simon Glasscc311ac2019-10-31 07:42:50 -060053 return self
54
Simon Glass840be732022-01-29 14:14:05 -070055def run_pipe(pipe_list, infile=None, outfile=None,
Simon Glass34e59432012-12-15 10:42:04 +000056 capture=False, capture_stderr=False, oneline=False,
Simon Glass146b6022021-10-19 21:43:24 -060057 raise_on_error=True, cwd=None, binary=False,
58 output_func=None, **kwargs):
Simon Glass26132882012-01-14 15:12:45 +000059 """
60 Perform a command pipeline, with optional input/output filenames.
61
Simon Glass34e59432012-12-15 10:42:04 +000062 Args:
63 pipe_list: List of command lines to execute. Each command line is
64 piped into the next, and is itself a list of strings. For
65 example [ ['ls', '.git'] ['wc'] ] will pipe the output of
66 'ls .git' into 'wc'.
67 infile: File to provide stdin to the pipeline
68 outfile: File to store stdout
69 capture: True to capture output
70 capture_stderr: True to capture stderr
71 oneline: True to strip newline chars from output
Simon Glass146b6022021-10-19 21:43:24 -060072 output_func: Output function to call with each output fragment
73 (if it returns True the function terminates)
Simon Glass34e59432012-12-15 10:42:04 +000074 kwargs: Additional keyword arguments to cros_subprocess.Popen()
75 Returns:
76 CommandResult object
Simon Glass82327692025-02-03 09:26:43 -070077 Raises:
78 CommandExc if an exception happens
Simon Glass26132882012-01-14 15:12:45 +000079 """
Simon Glass5dc22cf2025-02-03 09:26:42 -070080 if TEST_RESULT:
81 if hasattr(TEST_RESULT, '__call__'):
Simon Glass547cba62022-02-11 13:23:18 -070082 # pylint: disable=E1102
Simon Glass5dc22cf2025-02-03 09:26:42 -070083 result = TEST_RESULT(pipe_list=pipe_list)
Simon Glass6a8480b2018-07-17 13:25:42 -060084 if result:
85 return result
86 else:
Simon Glass5dc22cf2025-02-03 09:26:42 -070087 return TEST_RESULT
Simon Glass6a8480b2018-07-17 13:25:42 -060088 # No result: fall through to normal processing
Simon Glasscc311ac2019-10-31 07:42:50 -060089 result = CommandResult(b'', b'', b'')
Simon Glass26132882012-01-14 15:12:45 +000090 last_pipe = None
Simon Glass34e59432012-12-15 10:42:04 +000091 pipeline = list(pipe_list)
Simon Glass519fad22012-12-15 10:42:05 +000092 user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list])
Simon Glassf1bf6862014-09-05 19:00:09 -060093 kwargs['stdout'] = None
94 kwargs['stderr'] = None
Simon Glass26132882012-01-14 15:12:45 +000095 while pipeline:
96 cmd = pipeline.pop(0)
Simon Glass26132882012-01-14 15:12:45 +000097 if last_pipe is not None:
98 kwargs['stdin'] = last_pipe.stdout
99 elif infile:
100 kwargs['stdin'] = open(infile, 'rb')
101 if pipeline or capture:
Simon Glass34e59432012-12-15 10:42:04 +0000102 kwargs['stdout'] = cros_subprocess.PIPE
Simon Glass26132882012-01-14 15:12:45 +0000103 elif outfile:
104 kwargs['stdout'] = open(outfile, 'wb')
Simon Glass34e59432012-12-15 10:42:04 +0000105 if capture_stderr:
106 kwargs['stderr'] = cros_subprocess.PIPE
Simon Glass26132882012-01-14 15:12:45 +0000107
Simon Glass34e59432012-12-15 10:42:04 +0000108 try:
109 last_pipe = cros_subprocess.Popen(cmd, cwd=cwd, **kwargs)
Paul Burtonf14a1312016-09-27 16:03:51 +0100110 except Exception as err:
Simon Glass34e59432012-12-15 10:42:04 +0000111 result.exception = err
Simon Glass519fad22012-12-15 10:42:05 +0000112 if raise_on_error:
Simon Glass82327692025-02-03 09:26:43 -0700113 raise CommandExc(f"Error running '{user_pipestr}': {err}",
114 result) from err
Simon Glass519fad22012-12-15 10:42:05 +0000115 result.return_code = 255
Simon Glass840be732022-01-29 14:14:05 -0700116 return result.to_output(binary)
Simon Glass26132882012-01-14 15:12:45 +0000117
118 if capture:
Simon Glass34e59432012-12-15 10:42:04 +0000119 result.stdout, result.stderr, result.combined = (
Simon Glass4c0557b2022-01-29 14:14:08 -0700120 last_pipe.communicate_filter(output_func))
Simon Glass34e59432012-12-15 10:42:04 +0000121 if result.stdout and oneline:
Simon Glasscc311ac2019-10-31 07:42:50 -0600122 result.output = result.stdout.rstrip(b'\r\n')
Simon Glass85bbeeb2023-11-01 11:17:50 -0600123 result.return_code = last_pipe.wait()
Simon Glass519fad22012-12-15 10:42:05 +0000124 if raise_on_error and result.return_code:
Simon Glass82327692025-02-03 09:26:43 -0700125 raise CommandExc(f"Error running '{user_pipestr}'", result)
Simon Glass840be732022-01-29 14:14:05 -0700126 return result.to_output(binary)
Simon Glass26132882012-01-14 15:12:45 +0000127
Simon Glass840be732022-01-29 14:14:05 -0700128def output(*cmd, **kwargs):
Simon Glass097483d2019-07-08 13:18:23 -0600129 kwargs['raise_on_error'] = kwargs.get('raise_on_error', True)
Simon Glass840be732022-01-29 14:14:05 -0700130 return run_pipe([cmd], capture=True, **kwargs).stdout
Simon Glass26132882012-01-14 15:12:45 +0000131
Simon Glass840be732022-01-29 14:14:05 -0700132def output_one_line(*cmd, **kwargs):
Simon Glasscc311ac2019-10-31 07:42:50 -0600133 """Run a command and output it as a single-line string
134
135 The command us expected to produce a single line of output
136
137 Returns:
138 String containing output of command
139 """
Simon Glass519fad22012-12-15 10:42:05 +0000140 raise_on_error = kwargs.pop('raise_on_error', True)
Simon Glass840be732022-01-29 14:14:05 -0700141 result = run_pipe([cmd], capture=True, oneline=True,
Simon Glasscc311ac2019-10-31 07:42:50 -0600142 raise_on_error=raise_on_error, **kwargs).stdout.strip()
143 return result
Simon Glass26132882012-01-14 15:12:45 +0000144
Simon Glass840be732022-01-29 14:14:05 -0700145def run(*cmd, **kwargs):
146 return run_pipe([cmd], **kwargs).stdout
Simon Glass26132882012-01-14 15:12:45 +0000147
Simon Glass840be732022-01-29 14:14:05 -0700148def run_list(cmd):
149 return run_pipe([cmd], capture=True).stdout
Simon Glass34e59432012-12-15 10:42:04 +0000150
Simon Glass840be732022-01-29 14:14:05 -0700151def stop_all():
Simon Glass34e59432012-12-15 10:42:04 +0000152 cros_subprocess.stay_alive = False