blob: 2ea48bf58d617a30a27fb53287df8ede219bf5b6 [file] [log] [blame]
Scott Zawalski6bc41ac2010-09-08 12:47:28 -07001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Common python commands used by various build scripts."""
6
Chris Sosa471532a2011-02-01 15:10:06 -08007import inspect
Scott Zawalski98ac6b22010-09-08 15:59:23 -07008import os
Tan Gao2990a4d2010-09-22 09:34:27 -07009import re
Doug Anderson6781f942011-01-14 16:21:39 -080010import signal
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070011import subprocess
12import sys
Simon Glass53ed2302011-02-08 18:42:16 -080013from terminal import Color
14
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070015
16_STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
17
Tan Gao2f310882010-09-10 14:50:47 -070018
19class CommandResult(object):
20 """An object to store various attributes of a child process."""
21
22 def __init__(self):
23 self.cmd = None
24 self.error = None
25 self.output = None
26 self.returncode = None
27
28
29class RunCommandError(Exception):
30 """Error caught in RunCommand() method."""
31 pass
32
33
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070034def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
35 exit_code=False, redirect_stdout=False, redirect_stderr=False,
Doug Anderson6781f942011-01-14 16:21:39 -080036 cwd=None, input=None, enter_chroot=False, shell=False,
Chris Sosa66c8c252011-02-17 11:44:09 -080037 env=None, ignore_sigint=False, combine_stdout_stderr=False):
David James6db8f522010-09-09 10:49:11 -070038 """Runs a command.
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070039
Tan Gao2990a4d2010-09-22 09:34:27 -070040 Args:
41 cmd: cmd to run. Should be input to subprocess.Popen.
42 print_cmd: prints the command before running it.
43 error_ok: does not raise an exception on error.
44 error_message: prints out this message when an error occurrs.
45 exit_code: returns the return code of the shell command.
46 redirect_stdout: returns the stdout.
47 redirect_stderr: holds stderr output until input is communicated.
48 cwd: the working directory to run this cmd.
49 input: input to pipe into this command through stdin.
50 enter_chroot: this command should be run from within the chroot. If set,
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070051 cwd must point to the scripts directory.
Tan Gao2990a4d2010-09-22 09:34:27 -070052 shell: If shell is True, the specified command will be executed through
53 the shell.
Doug Anderson6781f942011-01-14 16:21:39 -080054 env: If non-None, this is the environment for the new process.
55 ignore_sigint: If True, we'll ignore signal.SIGINT before calling the
56 child. This is the desired behavior if we know our child will handle
57 Ctrl-C. If we don't do this, I think we and the child will both get
58 Ctrl-C at the same time, which means we'll forcefully kill the child.
Chris Sosa66c8c252011-02-17 11:44:09 -080059 combine_stdout_stderr: Combines stdout and stdin streams into stdout.
Tan Gao2990a4d2010-09-22 09:34:27 -070060
61 Returns:
62 A CommandResult object.
63
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070064 Raises:
65 Exception: Raises generic exception on error with optional error_message.
66 """
67 # Set default for variables.
68 stdout = None
69 stderr = None
70 stdin = None
Tan Gao2f310882010-09-10 14:50:47 -070071 cmd_result = CommandResult()
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070072
73 # Modify defaults based on parameters.
Tan Gao2990a4d2010-09-22 09:34:27 -070074 if redirect_stdout: stdout = subprocess.PIPE
75 if redirect_stderr: stderr = subprocess.PIPE
Chris Sosa66c8c252011-02-17 11:44:09 -080076 if combine_stdout_stderr: stderr = subprocess.STDOUT
Tan Gao2990a4d2010-09-22 09:34:27 -070077 # TODO(sosa): gpylint complains about redefining built-in 'input'.
78 # Can we rename this variable?
79 if input: stdin = subprocess.PIPE
David James6db8f522010-09-09 10:49:11 -070080 if isinstance(cmd, basestring):
81 if enter_chroot: cmd = './enter_chroot.sh -- ' + cmd
82 cmd_str = cmd
83 else:
84 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
85 cmd_str = ' '.join(cmd)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070086
87 # Print out the command before running.
88 if print_cmd:
David James6db8f522010-09-09 10:49:11 -070089 Info('RunCommand: %s' % cmd_str)
Doug Andersona8d22de2011-01-13 16:22:58 -080090 cmd_result.cmd = cmd
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070091
92 try:
David James9102a892010-12-02 10:21:49 -080093 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout,
Doug Anderson6781f942011-01-14 16:21:39 -080094 stderr=stderr, shell=shell, env=env)
95 if ignore_sigint:
96 old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
97 try:
98 (cmd_result.output, cmd_result.error) = proc.communicate(input)
99 finally:
100 if ignore_sigint:
101 signal.signal(signal.SIGINT, old_sigint)
102
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700103 if exit_code:
Tan Gao2f310882010-09-10 14:50:47 -0700104 cmd_result.returncode = proc.returncode
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700105
106 if not error_ok and proc.returncode:
Tan Gao2f310882010-09-10 14:50:47 -0700107 msg = ('Command "%s" failed.\n' % cmd_str +
108 (error_message or cmd_result.error or cmd_result.output or ''))
109 raise RunCommandError(msg)
Tan Gao2990a4d2010-09-22 09:34:27 -0700110 # TODO(sosa): is it possible not to use the catch-all Exception here?
111 except Exception, e:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700112 if not error_ok:
113 raise
114 else:
115 Warning(str(e))
116
Tan Gao2f310882010-09-10 14:50:47 -0700117 return cmd_result
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700118
119
Simon Glass77a59c02011-03-04 20:11:28 -0800120#TODO(sjg): Remove this in favor of operation.Die
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700121def Die(message):
122 """Emits a red error message and halts execution.
123
Tan Gao2990a4d2010-09-22 09:34:27 -0700124 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700125 message: The message to be emitted before exiting.
126 """
127 print >> sys.stderr, (
128 Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message))
129 sys.exit(1)
130
131
Simon Glass77a59c02011-03-04 20:11:28 -0800132#TODO(sjg): Remove this in favor of operation.Warning
Tan Gao2990a4d2010-09-22 09:34:27 -0700133# pylint: disable-msg=W0622
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700134def Warning(message):
135 """Emits a yellow warning message and continues execution.
136
Tan Gao2990a4d2010-09-22 09:34:27 -0700137 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700138 message: The message to be emitted.
139 """
140 print >> sys.stderr, (
141 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message))
142
143
Simon Glass77a59c02011-03-04 20:11:28 -0800144# This command is deprecated in favor of operation.Info()
145# It is left here for the moment so people are aware what happened.
146# The reason is that this is not aware of the terminal output restrictions such
147# as verbose, quiet and subprocess output. You should not be calling this.
148# def Info(message):
149# """Emits a blue informational message and continues execution.
150#
151# Args:
152# message: The message to be emitted.
153# """
154# print >> sys.stderr, (
155# Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message))
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700156
157
158def ListFiles(base_dir):
159 """Recurively list files in a directory.
160
Tan Gao2990a4d2010-09-22 09:34:27 -0700161 Args:
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700162 base_dir: directory to start recursively listing in.
163
164 Returns:
165 A list of files relative to the base_dir path or
166 An empty list of there are no files in the directories.
167 """
168 directories = [base_dir]
169 files_list = []
170 while directories:
171 directory = directories.pop()
172 for name in os.listdir(directory):
173 fullpath = os.path.join(directory, name)
174 if os.path.isfile(fullpath):
175 files_list.append(fullpath)
176 elif os.path.isdir(fullpath):
177 directories.append(fullpath)
178
179 return files_list
Tan Gao2990a4d2010-09-22 09:34:27 -0700180
181
182def IsInsideChroot():
183 """Returns True if we are inside chroot."""
184 return os.path.exists('/etc/debian_chroot')
185
186
187def GetSrcRoot():
188 """Get absolute path to src/scripts/ directory.
189
190 Assuming test script will always be run from descendent of src/scripts.
191
192 Returns:
193 A string, absolute path to src/scripts directory. None if not found.
194 """
195 src_root = None
196 match_str = '/src/scripts/'
197 test_script_path = os.path.abspath('.')
198
199 path_list = re.split(match_str, test_script_path)
200 if path_list:
201 src_root = os.path.join(path_list[0], match_str.strip('/'))
202 Info ('src_root = %r' % src_root)
203 else:
204 Info ('No %r found in %r' % (match_str, test_script_path))
205
206 return src_root
207
208
209def GetChromeosVersion(str_obj):
210 """Helper method to parse output for CHROMEOS_VERSION_STRING.
211
212 Args:
213 str_obj: a string, which may contain Chrome OS version info.
214
215 Returns:
216 A string, value of CHROMEOS_VERSION_STRING environment variable set by
217 chromeos_version.sh. Or None if not found.
218 """
219 if str_obj is not None:
220 match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj)
221 if match and match.group(1):
222 Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1))
223 return match.group(1)
224
225 Info ('CHROMEOS_VERSION_STRING NOT found')
226 return None
227
228
229def GetOutputImageDir(board, cros_version):
230 """Construct absolute path to output image directory.
231
232 Args:
233 board: a string.
234 cros_version: a string, Chrome OS version.
235
236 Returns:
237 a string: absolute path to output directory.
238 """
239 src_root = GetSrcRoot()
240 rel_path = 'build/images/%s' % board
241 # ASSUME: --build_attempt always sets to 1
242 version_str = '-'.join([cros_version, 'a1'])
243 output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str)
244 Info ('output_dir = %s' % output_dir)
245 return output_dir
Chris Sosa471532a2011-02-01 15:10:06 -0800246
247
248def FindRepoDir(path=None):
249 """Returns the nearest higher-level repo dir from the specified path.
250
251 Args:
252 path: The path to use. Defaults to cwd.
253 """
254 if path is None:
255 path = os.getcwd()
256 path = os.path.abspath(path)
257 while path != '/':
258 repo_dir = os.path.join(path, '.repo')
259 if os.path.isdir(repo_dir):
260 return repo_dir
261 path = os.path.dirname(path)
262 return None
263
264
265def ReinterpretPathForChroot(path):
266 """Returns reinterpreted path from outside the chroot for use inside.
267
268 Args:
269 path: The path to reinterpret. Must be in src tree.
270 """
271 root_path = os.path.join(FindRepoDir(path), '..')
272
273 path_abs_path = os.path.abspath(path)
274 root_abs_path = os.path.abspath(root_path)
275
276 # Strip the repository root from the path and strip first /.
277 relative_path = path_abs_path.replace(root_abs_path, '')[1:]
278
279 if relative_path == path_abs_path:
280 raise Exception('Error: path is outside your src tree, cannot reinterpret.')
281
282 new_path = os.path.join('/home', os.getenv('USER'), 'trunk', relative_path)
283 return new_path
284
285
286def GetCallerName():
287 """Returns the name of the calling module with __main__."""
288 top_frame = inspect.stack()[-1][0]
289 return os.path.basename(top_frame.f_code.co_filename)
290
291
292class RunCommandException(Exception):
293 """Raised when there is an error in OldRunCommand."""
294 pass
295
296
297def OldRunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
298 exit_code=False, redirect_stdout=False, redirect_stderr=False,
299 cwd=None, input=None, enter_chroot=False, num_retries=0):
300 """Legacy run shell command.
301
302 Arguments:
303 cmd: cmd to run. Should be input to subprocess.POpen. If a string,
304 converted to an array using split().
305 print_cmd: prints the command before running it.
306 error_ok: does not raise an exception on error.
307 error_message: prints out this message when an error occurrs.
308 exit_code: returns the return code of the shell command.
309 redirect_stdout: returns the stdout.
310 redirect_stderr: holds stderr output until input is communicated.
311 cwd: the working directory to run this cmd.
312 input: input to pipe into this command through stdin.
313 enter_chroot: this command should be run from within the chroot. If set,
314 cwd must point to the scripts directory.
315 num_retries: the number of retries to perform before dying
316
317 Returns:
318 If exit_code is True, returns the return code of the shell command.
319 Else returns the output of the shell command.
320
321 Raises:
322 Exception: Raises RunCommandException on error with optional error_message.
323 """
324 # Set default for variables.
325 stdout = None
326 stderr = None
327 stdin = None
328 output = ''
329
330 # Modify defaults based on parameters.
331 if redirect_stdout: stdout = subprocess.PIPE
332 if redirect_stderr: stderr = subprocess.PIPE
333 if input: stdin = subprocess.PIPE
334 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
335
336 # Print out the command before running.
337 if print_cmd:
338 Info('PROGRAM(%s) -> RunCommand: %r in dir %s' %
339 (GetCallerName(), cmd, cwd))
340
341 for retry_count in range(num_retries + 1):
342 try:
343 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin,
344 stdout=stdout, stderr=stderr)
345 (output, error) = proc.communicate(input)
346 if exit_code and retry_count == num_retries:
347 return proc.returncode
348
349 if proc.returncode == 0:
350 break
351
352 raise RunCommandException('Command "%r" failed.\n' % (cmd) +
353 (error_message or error or output or ''))
354 except RunCommandException as e:
355 if not error_ok and retry_count == num_retries:
356 raise e
357 else:
358 Warning(str(e))
359 if print_cmd:
360 Info('PROGRAM(%s) -> RunCommand: retrying %r in dir %s' %
361 (GetCallerName(), cmd, cwd))
362
363 return output