blob: 9e35dbc0e599850edcf6671a606d446c8eb4991c [file] [log] [blame]
Peter Mayo193f68f2011-04-19 19:08:21 -04001 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
Scott Zawalski6bc41ac2010-09-08 12:47:28 -07002# 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,
Peter Mayo193f68f2011-04-19 19:08:21 -040046 env=None, extra_env=None, ignore_sigint=False,
47 combine_stdout_stderr=False):
David James6db8f522010-09-09 10:49:11 -070048 """Runs a command.
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070049
Tan Gao2990a4d2010-09-22 09:34:27 -070050 Args:
Peter Mayo193f68f2011-04-19 19:08:21 -040051 cmd: cmd to run. Should be input to subprocess.Popen. If a string, shell
52 must be true. Otherwise the command must be an array of arguments, and
53 shell must be false.
Tan Gao2990a4d2010-09-22 09:34:27 -070054 print_cmd: prints the command before running it.
55 error_ok: does not raise an exception on error.
56 error_message: prints out this message when an error occurrs.
57 exit_code: returns the return code of the shell command.
58 redirect_stdout: returns the stdout.
59 redirect_stderr: holds stderr output until input is communicated.
60 cwd: the working directory to run this cmd.
61 input: input to pipe into this command through stdin.
62 enter_chroot: this command should be run from within the chroot. If set,
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070063 cwd must point to the scripts directory.
Peter Mayo193f68f2011-04-19 19:08:21 -040064 shell: Controls whether we add a shell as a command interpreter. See cmd
65 since it has to agree as to the type.
66 env: If non-None, this is the environment for the new process. If
67 enter_chroot is true then this is the environment of the enter_chroot,
68 most of which gets removed from the cmd run.
69 extra_env: If set, this is added to the environment for the new process.
70 In enter_chroot=True case, these are specified on the post-entry
71 side, and so are often more useful. This dictionary is not used to
72 clear any entries though.
Doug Anderson6781f942011-01-14 16:21:39 -080073 ignore_sigint: If True, we'll ignore signal.SIGINT before calling the
74 child. This is the desired behavior if we know our child will handle
75 Ctrl-C. If we don't do this, I think we and the child will both get
76 Ctrl-C at the same time, which means we'll forcefully kill the child.
Chris Sosa66c8c252011-02-17 11:44:09 -080077 combine_stdout_stderr: Combines stdout and stdin streams into stdout.
Tan Gao2990a4d2010-09-22 09:34:27 -070078
79 Returns:
80 A CommandResult object.
81
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070082 Raises:
83 Exception: Raises generic exception on error with optional error_message.
84 """
85 # Set default for variables.
86 stdout = None
87 stderr = None
88 stdin = None
Tan Gao2f310882010-09-10 14:50:47 -070089 cmd_result = CommandResult()
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070090
91 # Modify defaults based on parameters.
Tan Gao2990a4d2010-09-22 09:34:27 -070092 if redirect_stdout: stdout = subprocess.PIPE
93 if redirect_stderr: stderr = subprocess.PIPE
Chris Sosa66c8c252011-02-17 11:44:09 -080094 if combine_stdout_stderr: stderr = subprocess.STDOUT
Tan Gao2990a4d2010-09-22 09:34:27 -070095 # TODO(sosa): gpylint complains about redefining built-in 'input'.
96 # Can we rename this variable?
97 if input: stdin = subprocess.PIPE
Peter Mayo193f68f2011-04-19 19:08:21 -040098
David James6db8f522010-09-09 10:49:11 -070099 if isinstance(cmd, basestring):
Peter Mayo193f68f2011-04-19 19:08:21 -0400100 if not shell:
101 raise Exception('Cannot run a string command without a shell')
102 cmd = ['/bin/sh', '-c', cmd]
103 shell = False
104 elif shell:
105 raise Exception('Cannot run an array command with a shell')
106
107 # If we are using enter_chroot we need to use enterchroot pass env through
108 # to the final command.
109 if enter_chroot:
110 cmd = ['./enter_chroot.sh', '--'] + cmd
111 if extra_env:
112 for (key, value) in extra_env.items():
113 cmd.insert(1, '%s=%s' % (key, value))
114 elif extra_env:
115 if env is not None:
116 env = env.copy()
117 else:
118 env = os.environ.copy()
119
120 env.update(extra_env)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700121
122 # Print out the command before running.
123 if print_cmd:
Don Garrettf3eac242011-04-13 17:50:20 -0700124 if cwd:
Peter Mayo193f68f2011-04-19 19:08:21 -0400125 Info('RunCommand: %r in %s' % (cmd, cwd))
Don Garrettf3eac242011-04-13 17:50:20 -0700126 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400127 Info('RunCommand: %r' % cmd)
Doug Andersona8d22de2011-01-13 16:22:58 -0800128 cmd_result.cmd = cmd
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700129
130 try:
David James9102a892010-12-02 10:21:49 -0800131 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout,
Peter Mayo193f68f2011-04-19 19:08:21 -0400132 stderr=stderr, shell=False, env=env)
Doug Anderson6781f942011-01-14 16:21:39 -0800133 if ignore_sigint:
134 old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
135 try:
136 (cmd_result.output, cmd_result.error) = proc.communicate(input)
137 finally:
138 if ignore_sigint:
139 signal.signal(signal.SIGINT, old_sigint)
140
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700141 if exit_code:
Tan Gao2f310882010-09-10 14:50:47 -0700142 cmd_result.returncode = proc.returncode
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700143
144 if not error_ok and proc.returncode:
Peter Mayo193f68f2011-04-19 19:08:21 -0400145 msg = ('Command "%r" failed.\n' % cmd +
Tan Gao2f310882010-09-10 14:50:47 -0700146 (error_message or cmd_result.error or cmd_result.output or ''))
Don Garrettb85946a2011-03-10 18:11:08 -0800147 raise RunCommandError(msg, cmd)
Tan Gao2990a4d2010-09-22 09:34:27 -0700148 # TODO(sosa): is it possible not to use the catch-all Exception here?
Peter Mayo193f68f2011-04-19 19:08:21 -0400149 except OSError, e:
150 if not error_ok:
151 raise RunCommandError(str(e), cmd)
152 else:
153 Warning(str(e))
Tan Gao2990a4d2010-09-22 09:34:27 -0700154 except Exception, e:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700155 if not error_ok:
156 raise
157 else:
158 Warning(str(e))
159
Tan Gao2f310882010-09-10 14:50:47 -0700160 return cmd_result
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700161
162
Simon Glass5329b932011-03-14 16:49:04 -0700163#TODO(sjg): Remove this in favor of operation.Die
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700164def Die(message):
165 """Emits a red error message and halts execution.
166
Tan Gao2990a4d2010-09-22 09:34:27 -0700167 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700168 message: The message to be emitted before exiting.
169 """
170 print >> sys.stderr, (
171 Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message))
172 sys.exit(1)
173
174
Simon Glass5329b932011-03-14 16:49:04 -0700175#TODO(sjg): Remove this in favor of operation.Warning
Tan Gao2990a4d2010-09-22 09:34:27 -0700176# pylint: disable-msg=W0622
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700177def Warning(message):
178 """Emits a yellow warning message and continues execution.
179
Tan Gao2990a4d2010-09-22 09:34:27 -0700180 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700181 message: The message to be emitted.
182 """
183 print >> sys.stderr, (
184 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message))
185
186
Simon Glass5329b932011-03-14 16:49:04 -0700187# This command is deprecated in favor of operation.Info()
188# It is left here for the moment so people are aware what happened.
189# The reason is that this is not aware of the terminal output restrictions such
190# as verbose, quiet and subprocess output. You should not be calling this.
Simon Glass6b069ef2011-03-14 17:24:10 -0700191def Info(message):
192 """Emits a blue informational message and continues execution.
193
194 Args:
195 message: The message to be emitted.
196 """
197 print >> sys.stderr, (
198 Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message))
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700199
200
201def ListFiles(base_dir):
202 """Recurively list files in a directory.
203
Tan Gao2990a4d2010-09-22 09:34:27 -0700204 Args:
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700205 base_dir: directory to start recursively listing in.
206
207 Returns:
208 A list of files relative to the base_dir path or
209 An empty list of there are no files in the directories.
210 """
211 directories = [base_dir]
212 files_list = []
213 while directories:
214 directory = directories.pop()
215 for name in os.listdir(directory):
216 fullpath = os.path.join(directory, name)
217 if os.path.isfile(fullpath):
218 files_list.append(fullpath)
219 elif os.path.isdir(fullpath):
220 directories.append(fullpath)
221
222 return files_list
Tan Gao2990a4d2010-09-22 09:34:27 -0700223
224
225def IsInsideChroot():
226 """Returns True if we are inside chroot."""
227 return os.path.exists('/etc/debian_chroot')
228
229
230def GetSrcRoot():
231 """Get absolute path to src/scripts/ directory.
232
233 Assuming test script will always be run from descendent of src/scripts.
234
235 Returns:
236 A string, absolute path to src/scripts directory. None if not found.
237 """
238 src_root = None
239 match_str = '/src/scripts/'
240 test_script_path = os.path.abspath('.')
241
242 path_list = re.split(match_str, test_script_path)
243 if path_list:
244 src_root = os.path.join(path_list[0], match_str.strip('/'))
245 Info ('src_root = %r' % src_root)
246 else:
247 Info ('No %r found in %r' % (match_str, test_script_path))
248
249 return src_root
250
251
252def GetChromeosVersion(str_obj):
253 """Helper method to parse output for CHROMEOS_VERSION_STRING.
254
255 Args:
256 str_obj: a string, which may contain Chrome OS version info.
257
258 Returns:
259 A string, value of CHROMEOS_VERSION_STRING environment variable set by
260 chromeos_version.sh. Or None if not found.
261 """
262 if str_obj is not None:
263 match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj)
264 if match and match.group(1):
265 Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1))
266 return match.group(1)
267
268 Info ('CHROMEOS_VERSION_STRING NOT found')
269 return None
270
271
272def GetOutputImageDir(board, cros_version):
273 """Construct absolute path to output image directory.
274
275 Args:
276 board: a string.
277 cros_version: a string, Chrome OS version.
278
279 Returns:
280 a string: absolute path to output directory.
281 """
282 src_root = GetSrcRoot()
283 rel_path = 'build/images/%s' % board
284 # ASSUME: --build_attempt always sets to 1
285 version_str = '-'.join([cros_version, 'a1'])
286 output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str)
287 Info ('output_dir = %s' % output_dir)
288 return output_dir
Chris Sosa471532a2011-02-01 15:10:06 -0800289
290
291def FindRepoDir(path=None):
292 """Returns the nearest higher-level repo dir from the specified path.
293
294 Args:
295 path: The path to use. Defaults to cwd.
296 """
297 if path is None:
298 path = os.getcwd()
299 path = os.path.abspath(path)
300 while path != '/':
301 repo_dir = os.path.join(path, '.repo')
302 if os.path.isdir(repo_dir):
303 return repo_dir
304 path = os.path.dirname(path)
305 return None
306
307
308def ReinterpretPathForChroot(path):
309 """Returns reinterpreted path from outside the chroot for use inside.
310
311 Args:
312 path: The path to reinterpret. Must be in src tree.
313 """
314 root_path = os.path.join(FindRepoDir(path), '..')
315
316 path_abs_path = os.path.abspath(path)
317 root_abs_path = os.path.abspath(root_path)
318
319 # Strip the repository root from the path and strip first /.
320 relative_path = path_abs_path.replace(root_abs_path, '')[1:]
321
322 if relative_path == path_abs_path:
323 raise Exception('Error: path is outside your src tree, cannot reinterpret.')
324
325 new_path = os.path.join('/home', os.getenv('USER'), 'trunk', relative_path)
326 return new_path
327
328
329def GetCallerName():
330 """Returns the name of the calling module with __main__."""
331 top_frame = inspect.stack()[-1][0]
332 return os.path.basename(top_frame.f_code.co_filename)
333
334
335class RunCommandException(Exception):
336 """Raised when there is an error in OldRunCommand."""
Don Garrettb85946a2011-03-10 18:11:08 -0800337 def __init__(self, msg, cmd):
338 self.cmd = cmd
339 Exception.__init__(self, msg)
340
341 def __eq__(self, other):
342 return (type(self) == type(other) and
343 str(self) == str(other) and
344 self.cmd == other.cmd)
345
346 def __ne__(self, other):
347 return not self.__eq__(other)
Chris Sosa471532a2011-02-01 15:10:06 -0800348
349
350def OldRunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
351 exit_code=False, redirect_stdout=False, redirect_stderr=False,
352 cwd=None, input=None, enter_chroot=False, num_retries=0):
353 """Legacy run shell command.
354
355 Arguments:
356 cmd: cmd to run. Should be input to subprocess.POpen. If a string,
357 converted to an array using split().
358 print_cmd: prints the command before running it.
359 error_ok: does not raise an exception on error.
360 error_message: prints out this message when an error occurrs.
361 exit_code: returns the return code of the shell command.
362 redirect_stdout: returns the stdout.
363 redirect_stderr: holds stderr output until input is communicated.
364 cwd: the working directory to run this cmd.
365 input: input to pipe into this command through stdin.
366 enter_chroot: this command should be run from within the chroot. If set,
367 cwd must point to the scripts directory.
368 num_retries: the number of retries to perform before dying
369
370 Returns:
371 If exit_code is True, returns the return code of the shell command.
372 Else returns the output of the shell command.
373
374 Raises:
375 Exception: Raises RunCommandException on error with optional error_message.
376 """
377 # Set default for variables.
378 stdout = None
379 stderr = None
380 stdin = None
381 output = ''
382
383 # Modify defaults based on parameters.
384 if redirect_stdout: stdout = subprocess.PIPE
385 if redirect_stderr: stderr = subprocess.PIPE
386 if input: stdin = subprocess.PIPE
387 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
388
389 # Print out the command before running.
390 if print_cmd:
391 Info('PROGRAM(%s) -> RunCommand: %r in dir %s' %
392 (GetCallerName(), cmd, cwd))
393
394 for retry_count in range(num_retries + 1):
395 try:
396 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin,
397 stdout=stdout, stderr=stderr)
398 (output, error) = proc.communicate(input)
399 if exit_code and retry_count == num_retries:
400 return proc.returncode
401
402 if proc.returncode == 0:
403 break
404
405 raise RunCommandException('Command "%r" failed.\n' % (cmd) +
Don Garrettb85946a2011-03-10 18:11:08 -0800406 (error_message or error or output or ''),
407 cmd)
Chris Sosa471532a2011-02-01 15:10:06 -0800408 except RunCommandException as e:
409 if not error_ok and retry_count == num_retries:
410 raise e
411 else:
412 Warning(str(e))
413 if print_cmd:
414 Info('PROGRAM(%s) -> RunCommand: retrying %r in dir %s' %
415 (GetCallerName(), cmd, cwd))
416
417 return output