blob: 1d2860ba93f8c8ae04b9467b8fba2c4e053b7da9 [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
Scott Zawalski98ac6b22010-09-08 15:59:23 -07007import os
Tan Gao2990a4d2010-09-22 09:34:27 -07008import re
Scott Zawalski6bc41ac2010-09-08 12:47:28 -07009import subprocess
10import sys
11
12_STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
13
Tan Gao2f310882010-09-10 14:50:47 -070014
15class CommandResult(object):
16 """An object to store various attributes of a child process."""
17
18 def __init__(self):
19 self.cmd = None
20 self.error = None
21 self.output = None
22 self.returncode = None
23
24
25class RunCommandError(Exception):
26 """Error caught in RunCommand() method."""
27 pass
28
29
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070030def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
31 exit_code=False, redirect_stdout=False, redirect_stderr=False,
David James6db8f522010-09-09 10:49:11 -070032 cwd=None, input=None, enter_chroot=False, shell=False):
33 """Runs a command.
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070034
Tan Gao2990a4d2010-09-22 09:34:27 -070035 Args:
36 cmd: cmd to run. Should be input to subprocess.Popen.
37 print_cmd: prints the command before running it.
38 error_ok: does not raise an exception on error.
39 error_message: prints out this message when an error occurrs.
40 exit_code: returns the return code of the shell command.
41 redirect_stdout: returns the stdout.
42 redirect_stderr: holds stderr output until input is communicated.
43 cwd: the working directory to run this cmd.
44 input: input to pipe into this command through stdin.
45 enter_chroot: this command should be run from within the chroot. If set,
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070046 cwd must point to the scripts directory.
Tan Gao2990a4d2010-09-22 09:34:27 -070047 shell: If shell is True, the specified command will be executed through
48 the shell.
49
50 Returns:
51 A CommandResult object.
52
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070053 Raises:
54 Exception: Raises generic exception on error with optional error_message.
55 """
56 # Set default for variables.
57 stdout = None
58 stderr = None
59 stdin = None
Tan Gao2f310882010-09-10 14:50:47 -070060 cmd_result = CommandResult()
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070061
62 # Modify defaults based on parameters.
Tan Gao2990a4d2010-09-22 09:34:27 -070063 if redirect_stdout: stdout = subprocess.PIPE
64 if redirect_stderr: stderr = subprocess.PIPE
65 # TODO(sosa): gpylint complains about redefining built-in 'input'.
66 # Can we rename this variable?
67 if input: stdin = subprocess.PIPE
David James6db8f522010-09-09 10:49:11 -070068 if isinstance(cmd, basestring):
69 if enter_chroot: cmd = './enter_chroot.sh -- ' + cmd
70 cmd_str = cmd
71 else:
72 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
73 cmd_str = ' '.join(cmd)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070074
75 # Print out the command before running.
76 if print_cmd:
David James6db8f522010-09-09 10:49:11 -070077 Info('RunCommand: %s' % cmd_str)
Tan Gao2f310882010-09-10 14:50:47 -070078 cmd_result.cmd = cmd_str
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070079
80 try:
Tan Gao2f310882010-09-10 14:50:47 -070081 proc = subprocess.Popen(cmd_str, cwd=cwd, stdin=stdin,
David James6db8f522010-09-09 10:49:11 -070082 stdout=stdout, stderr=stderr,
83 shell=shell)
Tan Gao2f310882010-09-10 14:50:47 -070084 (cmd_result.output, cmd_result.error) = proc.communicate(input)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070085 if exit_code:
Tan Gao2f310882010-09-10 14:50:47 -070086 cmd_result.returncode = proc.returncode
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070087
88 if not error_ok and proc.returncode:
Tan Gao2f310882010-09-10 14:50:47 -070089 msg = ('Command "%s" failed.\n' % cmd_str +
90 (error_message or cmd_result.error or cmd_result.output or ''))
91 raise RunCommandError(msg)
Tan Gao2990a4d2010-09-22 09:34:27 -070092 # TODO(sosa): is it possible not to use the catch-all Exception here?
93 except Exception, e:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070094 if not error_ok:
95 raise
96 else:
97 Warning(str(e))
98
Tan Gao2f310882010-09-10 14:50:47 -070099 return cmd_result
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700100
101
102class Color(object):
103 """Conditionally wraps text in ANSI color escape sequences."""
104 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
105 BOLD = -1
106 COLOR_START = '\033[1;%dm'
107 BOLD_START = '\033[1m'
108 RESET = '\033[0m'
109
110 def __init__(self, enabled=True):
111 self._enabled = enabled
112
113 def Color(self, color, text):
114 """Returns text with conditionally added color escape sequences.
115
Tan Gao2990a4d2010-09-22 09:34:27 -0700116 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700117 color: Text color -- one of the color constants defined in this class.
118 text: The text to color.
119
120 Returns:
121 If self._enabled is False, returns the original text. If it's True,
122 returns text with color escape sequences based on the value of color.
123 """
124 if not self._enabled:
125 return text
126 if color == self.BOLD:
127 start = self.BOLD_START
128 else:
129 start = self.COLOR_START % (color + 30)
130 return start + text + self.RESET
131
132
133def Die(message):
134 """Emits a red error message and halts execution.
135
Tan Gao2990a4d2010-09-22 09:34:27 -0700136 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700137 message: The message to be emitted before exiting.
138 """
139 print >> sys.stderr, (
140 Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message))
141 sys.exit(1)
142
143
Tan Gao2990a4d2010-09-22 09:34:27 -0700144# pylint: disable-msg=W0622
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700145def Warning(message):
146 """Emits a yellow warning message and continues execution.
147
Tan Gao2990a4d2010-09-22 09:34:27 -0700148 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700149 message: The message to be emitted.
150 """
151 print >> sys.stderr, (
152 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message))
153
154
155def Info(message):
156 """Emits a blue informational message and continues execution.
157
Tan Gao2990a4d2010-09-22 09:34:27 -0700158 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700159 message: The message to be emitted.
160 """
161 print >> sys.stderr, (
162 Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message))
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700163
164
165def ListFiles(base_dir):
166 """Recurively list files in a directory.
167
Tan Gao2990a4d2010-09-22 09:34:27 -0700168 Args:
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700169 base_dir: directory to start recursively listing in.
170
171 Returns:
172 A list of files relative to the base_dir path or
173 An empty list of there are no files in the directories.
174 """
175 directories = [base_dir]
176 files_list = []
177 while directories:
178 directory = directories.pop()
179 for name in os.listdir(directory):
180 fullpath = os.path.join(directory, name)
181 if os.path.isfile(fullpath):
182 files_list.append(fullpath)
183 elif os.path.isdir(fullpath):
184 directories.append(fullpath)
185
186 return files_list
Tan Gao2990a4d2010-09-22 09:34:27 -0700187
188
189def IsInsideChroot():
190 """Returns True if we are inside chroot."""
191 return os.path.exists('/etc/debian_chroot')
192
193
194def GetSrcRoot():
195 """Get absolute path to src/scripts/ directory.
196
197 Assuming test script will always be run from descendent of src/scripts.
198
199 Returns:
200 A string, absolute path to src/scripts directory. None if not found.
201 """
202 src_root = None
203 match_str = '/src/scripts/'
204 test_script_path = os.path.abspath('.')
205
206 path_list = re.split(match_str, test_script_path)
207 if path_list:
208 src_root = os.path.join(path_list[0], match_str.strip('/'))
209 Info ('src_root = %r' % src_root)
210 else:
211 Info ('No %r found in %r' % (match_str, test_script_path))
212
213 return src_root
214
215
216def GetChromeosVersion(str_obj):
217 """Helper method to parse output for CHROMEOS_VERSION_STRING.
218
219 Args:
220 str_obj: a string, which may contain Chrome OS version info.
221
222 Returns:
223 A string, value of CHROMEOS_VERSION_STRING environment variable set by
224 chromeos_version.sh. Or None if not found.
225 """
226 if str_obj is not None:
227 match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj)
228 if match and match.group(1):
229 Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1))
230 return match.group(1)
231
232 Info ('CHROMEOS_VERSION_STRING NOT found')
233 return None
234
235
236def GetOutputImageDir(board, cros_version):
237 """Construct absolute path to output image directory.
238
239 Args:
240 board: a string.
241 cros_version: a string, Chrome OS version.
242
243 Returns:
244 a string: absolute path to output directory.
245 """
246 src_root = GetSrcRoot()
247 rel_path = 'build/images/%s' % board
248 # ASSUME: --build_attempt always sets to 1
249 version_str = '-'.join([cros_version, 'a1'])
250 output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str)
251 Info ('output_dir = %s' % output_dir)
252 return output_dir