blob: 87fa133b75cab27180bd1c575ec2fe655fc78e45 [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:
David James9102a892010-12-02 10:21:49 -080081 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout,
82 stderr=stderr, shell=shell)
Tan Gao2f310882010-09-10 14:50:47 -070083 (cmd_result.output, cmd_result.error) = proc.communicate(input)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070084 if exit_code:
Tan Gao2f310882010-09-10 14:50:47 -070085 cmd_result.returncode = proc.returncode
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070086
87 if not error_ok and proc.returncode:
Tan Gao2f310882010-09-10 14:50:47 -070088 msg = ('Command "%s" failed.\n' % cmd_str +
89 (error_message or cmd_result.error or cmd_result.output or ''))
90 raise RunCommandError(msg)
Tan Gao2990a4d2010-09-22 09:34:27 -070091 # TODO(sosa): is it possible not to use the catch-all Exception here?
92 except Exception, e:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070093 if not error_ok:
94 raise
95 else:
96 Warning(str(e))
97
Tan Gao2f310882010-09-10 14:50:47 -070098 return cmd_result
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070099
100
101class Color(object):
102 """Conditionally wraps text in ANSI color escape sequences."""
103 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
104 BOLD = -1
105 COLOR_START = '\033[1;%dm'
106 BOLD_START = '\033[1m'
107 RESET = '\033[0m'
108
109 def __init__(self, enabled=True):
110 self._enabled = enabled
111
112 def Color(self, color, text):
113 """Returns text with conditionally added color escape sequences.
114
Tan Gao2990a4d2010-09-22 09:34:27 -0700115 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700116 color: Text color -- one of the color constants defined in this class.
117 text: The text to color.
118
119 Returns:
120 If self._enabled is False, returns the original text. If it's True,
121 returns text with color escape sequences based on the value of color.
122 """
123 if not self._enabled:
124 return text
125 if color == self.BOLD:
126 start = self.BOLD_START
127 else:
128 start = self.COLOR_START % (color + 30)
129 return start + text + self.RESET
130
131
132def Die(message):
133 """Emits a red error message and halts execution.
134
Tan Gao2990a4d2010-09-22 09:34:27 -0700135 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700136 message: The message to be emitted before exiting.
137 """
138 print >> sys.stderr, (
139 Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message))
140 sys.exit(1)
141
142
Tan Gao2990a4d2010-09-22 09:34:27 -0700143# pylint: disable-msg=W0622
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700144def Warning(message):
145 """Emits a yellow warning message and continues execution.
146
Tan Gao2990a4d2010-09-22 09:34:27 -0700147 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700148 message: The message to be emitted.
149 """
150 print >> sys.stderr, (
151 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message))
152
153
154def Info(message):
155 """Emits a blue informational message and continues execution.
156
Tan Gao2990a4d2010-09-22 09:34:27 -0700157 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700158 message: The message to be emitted.
159 """
160 print >> sys.stderr, (
161 Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message))
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700162
163
164def ListFiles(base_dir):
165 """Recurively list files in a directory.
166
Tan Gao2990a4d2010-09-22 09:34:27 -0700167 Args:
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700168 base_dir: directory to start recursively listing in.
169
170 Returns:
171 A list of files relative to the base_dir path or
172 An empty list of there are no files in the directories.
173 """
174 directories = [base_dir]
175 files_list = []
176 while directories:
177 directory = directories.pop()
178 for name in os.listdir(directory):
179 fullpath = os.path.join(directory, name)
180 if os.path.isfile(fullpath):
181 files_list.append(fullpath)
182 elif os.path.isdir(fullpath):
183 directories.append(fullpath)
184
185 return files_list
Tan Gao2990a4d2010-09-22 09:34:27 -0700186
187
188def IsInsideChroot():
189 """Returns True if we are inside chroot."""
190 return os.path.exists('/etc/debian_chroot')
191
192
193def GetSrcRoot():
194 """Get absolute path to src/scripts/ directory.
195
196 Assuming test script will always be run from descendent of src/scripts.
197
198 Returns:
199 A string, absolute path to src/scripts directory. None if not found.
200 """
201 src_root = None
202 match_str = '/src/scripts/'
203 test_script_path = os.path.abspath('.')
204
205 path_list = re.split(match_str, test_script_path)
206 if path_list:
207 src_root = os.path.join(path_list[0], match_str.strip('/'))
208 Info ('src_root = %r' % src_root)
209 else:
210 Info ('No %r found in %r' % (match_str, test_script_path))
211
212 return src_root
213
214
215def GetChromeosVersion(str_obj):
216 """Helper method to parse output for CHROMEOS_VERSION_STRING.
217
218 Args:
219 str_obj: a string, which may contain Chrome OS version info.
220
221 Returns:
222 A string, value of CHROMEOS_VERSION_STRING environment variable set by
223 chromeos_version.sh. Or None if not found.
224 """
225 if str_obj is not None:
226 match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj)
227 if match and match.group(1):
228 Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1))
229 return match.group(1)
230
231 Info ('CHROMEOS_VERSION_STRING NOT found')
232 return None
233
234
235def GetOutputImageDir(board, cros_version):
236 """Construct absolute path to output image directory.
237
238 Args:
239 board: a string.
240 cros_version: a string, Chrome OS version.
241
242 Returns:
243 a string: absolute path to output directory.
244 """
245 src_root = GetSrcRoot()
246 rel_path = 'build/images/%s' % board
247 # ASSUME: --build_attempt always sets to 1
248 version_str = '-'.join([cros_version, 'a1'])
249 output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str)
250 Info ('output_dir = %s' % output_dir)
251 return output_dir