blob: b388cda60bde4b8dbef5a9f020b7b842fc66414b [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."""
Don Garrettb85946a2011-03-10 18:11:08 -080031 def __init__(self, msg, cmd):
32 self.cmd = cmd
33 Exception.__init__(self, msg)
Tan Gao2f310882010-09-10 14:50:47 -070034
Don Garrettb85946a2011-03-10 18:11:08 -080035 def __eq__(self, other):
36 return (type(self) == type(other) and
37 str(self) == str(other) and
38 self.cmd == other.cmd)
39
40 def __ne__(self, other):
41 return not self.__eq__(other)
Tan Gao2f310882010-09-10 14:50:47 -070042
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070043def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
44 exit_code=False, redirect_stdout=False, redirect_stderr=False,
Doug Anderson6781f942011-01-14 16:21:39 -080045 cwd=None, input=None, enter_chroot=False, shell=False,
Chris Sosa66c8c252011-02-17 11:44:09 -080046 env=None, ignore_sigint=False, combine_stdout_stderr=False):
David James6db8f522010-09-09 10:49:11 -070047 """Runs a command.
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070048
Tan Gao2990a4d2010-09-22 09:34:27 -070049 Args:
50 cmd: cmd to run. Should be input to subprocess.Popen.
51 print_cmd: prints the command before running it.
52 error_ok: does not raise an exception on error.
53 error_message: prints out this message when an error occurrs.
54 exit_code: returns the return code of the shell command.
55 redirect_stdout: returns the stdout.
56 redirect_stderr: holds stderr output until input is communicated.
57 cwd: the working directory to run this cmd.
58 input: input to pipe into this command through stdin.
59 enter_chroot: this command should be run from within the chroot. If set,
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070060 cwd must point to the scripts directory.
Tan Gao2990a4d2010-09-22 09:34:27 -070061 shell: If shell is True, the specified command will be executed through
62 the shell.
Doug Anderson6781f942011-01-14 16:21:39 -080063 env: If non-None, this is the environment for the new process.
64 ignore_sigint: If True, we'll ignore signal.SIGINT before calling the
65 child. This is the desired behavior if we know our child will handle
66 Ctrl-C. If we don't do this, I think we and the child will both get
67 Ctrl-C at the same time, which means we'll forcefully kill the child.
Chris Sosa66c8c252011-02-17 11:44:09 -080068 combine_stdout_stderr: Combines stdout and stdin streams into stdout.
Tan Gao2990a4d2010-09-22 09:34:27 -070069
70 Returns:
71 A CommandResult object.
72
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070073 Raises:
74 Exception: Raises generic exception on error with optional error_message.
75 """
76 # Set default for variables.
77 stdout = None
78 stderr = None
79 stdin = None
Tan Gao2f310882010-09-10 14:50:47 -070080 cmd_result = CommandResult()
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070081
82 # Modify defaults based on parameters.
Tan Gao2990a4d2010-09-22 09:34:27 -070083 if redirect_stdout: stdout = subprocess.PIPE
84 if redirect_stderr: stderr = subprocess.PIPE
Chris Sosa66c8c252011-02-17 11:44:09 -080085 if combine_stdout_stderr: stderr = subprocess.STDOUT
Tan Gao2990a4d2010-09-22 09:34:27 -070086 # TODO(sosa): gpylint complains about redefining built-in 'input'.
87 # Can we rename this variable?
88 if input: stdin = subprocess.PIPE
David James6db8f522010-09-09 10:49:11 -070089 if isinstance(cmd, basestring):
90 if enter_chroot: cmd = './enter_chroot.sh -- ' + cmd
91 cmd_str = cmd
92 else:
93 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
94 cmd_str = ' '.join(cmd)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070095
96 # Print out the command before running.
97 if print_cmd:
David James6db8f522010-09-09 10:49:11 -070098 Info('RunCommand: %s' % cmd_str)
Doug Andersona8d22de2011-01-13 16:22:58 -080099 cmd_result.cmd = cmd
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700100
101 try:
David James9102a892010-12-02 10:21:49 -0800102 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout,
Doug Anderson6781f942011-01-14 16:21:39 -0800103 stderr=stderr, shell=shell, env=env)
104 if ignore_sigint:
105 old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
106 try:
107 (cmd_result.output, cmd_result.error) = proc.communicate(input)
108 finally:
109 if ignore_sigint:
110 signal.signal(signal.SIGINT, old_sigint)
111
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700112 if exit_code:
Tan Gao2f310882010-09-10 14:50:47 -0700113 cmd_result.returncode = proc.returncode
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700114
115 if not error_ok and proc.returncode:
Tan Gao2f310882010-09-10 14:50:47 -0700116 msg = ('Command "%s" failed.\n' % cmd_str +
117 (error_message or cmd_result.error or cmd_result.output or ''))
Don Garrettb85946a2011-03-10 18:11:08 -0800118 raise RunCommandError(msg, cmd)
Tan Gao2990a4d2010-09-22 09:34:27 -0700119 # TODO(sosa): is it possible not to use the catch-all Exception here?
120 except Exception, e:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700121 if not error_ok:
122 raise
123 else:
124 Warning(str(e))
125
Tan Gao2f310882010-09-10 14:50:47 -0700126 return cmd_result
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700127
128
Simon Glass5329b932011-03-14 16:49:04 -0700129#TODO(sjg): Remove this in favor of operation.Die
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700130def Die(message):
131 """Emits a red error message and halts execution.
132
Tan Gao2990a4d2010-09-22 09:34:27 -0700133 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700134 message: The message to be emitted before exiting.
135 """
136 print >> sys.stderr, (
137 Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message))
138 sys.exit(1)
139
140
Simon Glass5329b932011-03-14 16:49:04 -0700141#TODO(sjg): Remove this in favor of operation.Warning
Tan Gao2990a4d2010-09-22 09:34:27 -0700142# pylint: disable-msg=W0622
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700143def Warning(message):
144 """Emits a yellow warning message and continues execution.
145
Tan Gao2990a4d2010-09-22 09:34:27 -0700146 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700147 message: The message to be emitted.
148 """
149 print >> sys.stderr, (
150 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message))
151
152
Simon Glass5329b932011-03-14 16:49:04 -0700153# This command is deprecated in favor of operation.Info()
154# It is left here for the moment so people are aware what happened.
155# The reason is that this is not aware of the terminal output restrictions such
156# as verbose, quiet and subprocess output. You should not be calling this.
157# def Info(message):
158# """Emits a blue informational message and continues execution.
159#
160# Args:
161# message: The message to be emitted.
162# """
163# print >> sys.stderr, (
164# Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message))
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700165
166
167def ListFiles(base_dir):
168 """Recurively list files in a directory.
169
Tan Gao2990a4d2010-09-22 09:34:27 -0700170 Args:
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700171 base_dir: directory to start recursively listing in.
172
173 Returns:
174 A list of files relative to the base_dir path or
175 An empty list of there are no files in the directories.
176 """
177 directories = [base_dir]
178 files_list = []
179 while directories:
180 directory = directories.pop()
181 for name in os.listdir(directory):
182 fullpath = os.path.join(directory, name)
183 if os.path.isfile(fullpath):
184 files_list.append(fullpath)
185 elif os.path.isdir(fullpath):
186 directories.append(fullpath)
187
188 return files_list
Tan Gao2990a4d2010-09-22 09:34:27 -0700189
190
191def IsInsideChroot():
192 """Returns True if we are inside chroot."""
193 return os.path.exists('/etc/debian_chroot')
194
195
196def GetSrcRoot():
197 """Get absolute path to src/scripts/ directory.
198
199 Assuming test script will always be run from descendent of src/scripts.
200
201 Returns:
202 A string, absolute path to src/scripts directory. None if not found.
203 """
204 src_root = None
205 match_str = '/src/scripts/'
206 test_script_path = os.path.abspath('.')
207
208 path_list = re.split(match_str, test_script_path)
209 if path_list:
210 src_root = os.path.join(path_list[0], match_str.strip('/'))
211 Info ('src_root = %r' % src_root)
212 else:
213 Info ('No %r found in %r' % (match_str, test_script_path))
214
215 return src_root
216
217
218def GetChromeosVersion(str_obj):
219 """Helper method to parse output for CHROMEOS_VERSION_STRING.
220
221 Args:
222 str_obj: a string, which may contain Chrome OS version info.
223
224 Returns:
225 A string, value of CHROMEOS_VERSION_STRING environment variable set by
226 chromeos_version.sh. Or None if not found.
227 """
228 if str_obj is not None:
229 match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj)
230 if match and match.group(1):
231 Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1))
232 return match.group(1)
233
234 Info ('CHROMEOS_VERSION_STRING NOT found')
235 return None
236
237
238def GetOutputImageDir(board, cros_version):
239 """Construct absolute path to output image directory.
240
241 Args:
242 board: a string.
243 cros_version: a string, Chrome OS version.
244
245 Returns:
246 a string: absolute path to output directory.
247 """
248 src_root = GetSrcRoot()
249 rel_path = 'build/images/%s' % board
250 # ASSUME: --build_attempt always sets to 1
251 version_str = '-'.join([cros_version, 'a1'])
252 output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str)
253 Info ('output_dir = %s' % output_dir)
254 return output_dir
Chris Sosa471532a2011-02-01 15:10:06 -0800255
256
257def FindRepoDir(path=None):
258 """Returns the nearest higher-level repo dir from the specified path.
259
260 Args:
261 path: The path to use. Defaults to cwd.
262 """
263 if path is None:
264 path = os.getcwd()
265 path = os.path.abspath(path)
266 while path != '/':
267 repo_dir = os.path.join(path, '.repo')
268 if os.path.isdir(repo_dir):
269 return repo_dir
270 path = os.path.dirname(path)
271 return None
272
273
274def ReinterpretPathForChroot(path):
275 """Returns reinterpreted path from outside the chroot for use inside.
276
277 Args:
278 path: The path to reinterpret. Must be in src tree.
279 """
280 root_path = os.path.join(FindRepoDir(path), '..')
281
282 path_abs_path = os.path.abspath(path)
283 root_abs_path = os.path.abspath(root_path)
284
285 # Strip the repository root from the path and strip first /.
286 relative_path = path_abs_path.replace(root_abs_path, '')[1:]
287
288 if relative_path == path_abs_path:
289 raise Exception('Error: path is outside your src tree, cannot reinterpret.')
290
291 new_path = os.path.join('/home', os.getenv('USER'), 'trunk', relative_path)
292 return new_path
293
294
295def GetCallerName():
296 """Returns the name of the calling module with __main__."""
297 top_frame = inspect.stack()[-1][0]
298 return os.path.basename(top_frame.f_code.co_filename)
299
300
301class RunCommandException(Exception):
302 """Raised when there is an error in OldRunCommand."""
Don Garrettb85946a2011-03-10 18:11:08 -0800303 def __init__(self, msg, cmd):
304 self.cmd = cmd
305 Exception.__init__(self, msg)
306
307 def __eq__(self, other):
308 return (type(self) == type(other) and
309 str(self) == str(other) and
310 self.cmd == other.cmd)
311
312 def __ne__(self, other):
313 return not self.__eq__(other)
Chris Sosa471532a2011-02-01 15:10:06 -0800314
315
316def OldRunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
317 exit_code=False, redirect_stdout=False, redirect_stderr=False,
318 cwd=None, input=None, enter_chroot=False, num_retries=0):
319 """Legacy run shell command.
320
321 Arguments:
322 cmd: cmd to run. Should be input to subprocess.POpen. If a string,
323 converted to an array using split().
324 print_cmd: prints the command before running it.
325 error_ok: does not raise an exception on error.
326 error_message: prints out this message when an error occurrs.
327 exit_code: returns the return code of the shell command.
328 redirect_stdout: returns the stdout.
329 redirect_stderr: holds stderr output until input is communicated.
330 cwd: the working directory to run this cmd.
331 input: input to pipe into this command through stdin.
332 enter_chroot: this command should be run from within the chroot. If set,
333 cwd must point to the scripts directory.
334 num_retries: the number of retries to perform before dying
335
336 Returns:
337 If exit_code is True, returns the return code of the shell command.
338 Else returns the output of the shell command.
339
340 Raises:
341 Exception: Raises RunCommandException on error with optional error_message.
342 """
343 # Set default for variables.
344 stdout = None
345 stderr = None
346 stdin = None
347 output = ''
348
349 # Modify defaults based on parameters.
350 if redirect_stdout: stdout = subprocess.PIPE
351 if redirect_stderr: stderr = subprocess.PIPE
352 if input: stdin = subprocess.PIPE
353 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
354
355 # Print out the command before running.
356 if print_cmd:
357 Info('PROGRAM(%s) -> RunCommand: %r in dir %s' %
358 (GetCallerName(), cmd, cwd))
359
360 for retry_count in range(num_retries + 1):
361 try:
362 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin,
363 stdout=stdout, stderr=stderr)
364 (output, error) = proc.communicate(input)
365 if exit_code and retry_count == num_retries:
366 return proc.returncode
367
368 if proc.returncode == 0:
369 break
370
371 raise RunCommandException('Command "%r" failed.\n' % (cmd) +
Don Garrettb85946a2011-03-10 18:11:08 -0800372 (error_message or error or output or ''),
373 cmd)
Chris Sosa471532a2011-02-01 15:10:06 -0800374 except RunCommandException as e:
375 if not error_ok and retry_count == num_retries:
376 raise e
377 else:
378 Warning(str(e))
379 if print_cmd:
380 Info('PROGRAM(%s) -> RunCommand: retrying %r in dir %s' %
381 (GetCallerName(), cmd, cwd))
382
383 return output