blob: 819d2382f4202380e5bd0d4c808a4179a929c591 [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
Doug Anderson6781f942011-01-14 16:21:39 -08009import signal
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070010import subprocess
11import sys
12
13_STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
14
Tan Gao2f310882010-09-10 14:50:47 -070015
16class CommandResult(object):
17 """An object to store various attributes of a child process."""
18
19 def __init__(self):
20 self.cmd = None
21 self.error = None
22 self.output = None
23 self.returncode = None
24
25
26class RunCommandError(Exception):
27 """Error caught in RunCommand() method."""
28 pass
29
30
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070031def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
32 exit_code=False, redirect_stdout=False, redirect_stderr=False,
Doug Anderson6781f942011-01-14 16:21:39 -080033 cwd=None, input=None, enter_chroot=False, shell=False,
34 env=None, ignore_sigint=False):
David James6db8f522010-09-09 10:49:11 -070035 """Runs a command.
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070036
Tan Gao2990a4d2010-09-22 09:34:27 -070037 Args:
38 cmd: cmd to run. Should be input to subprocess.Popen.
39 print_cmd: prints the command before running it.
40 error_ok: does not raise an exception on error.
41 error_message: prints out this message when an error occurrs.
42 exit_code: returns the return code of the shell command.
43 redirect_stdout: returns the stdout.
44 redirect_stderr: holds stderr output until input is communicated.
45 cwd: the working directory to run this cmd.
46 input: input to pipe into this command through stdin.
47 enter_chroot: this command should be run from within the chroot. If set,
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070048 cwd must point to the scripts directory.
Tan Gao2990a4d2010-09-22 09:34:27 -070049 shell: If shell is True, the specified command will be executed through
50 the shell.
Doug Anderson6781f942011-01-14 16:21:39 -080051 env: If non-None, this is the environment for the new process.
52 ignore_sigint: If True, we'll ignore signal.SIGINT before calling the
53 child. This is the desired behavior if we know our child will handle
54 Ctrl-C. If we don't do this, I think we and the child will both get
55 Ctrl-C at the same time, which means we'll forcefully kill the child.
Tan Gao2990a4d2010-09-22 09:34:27 -070056
57 Returns:
58 A CommandResult object.
59
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070060 Raises:
61 Exception: Raises generic exception on error with optional error_message.
62 """
63 # Set default for variables.
64 stdout = None
65 stderr = None
66 stdin = None
Tan Gao2f310882010-09-10 14:50:47 -070067 cmd_result = CommandResult()
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070068
69 # Modify defaults based on parameters.
Tan Gao2990a4d2010-09-22 09:34:27 -070070 if redirect_stdout: stdout = subprocess.PIPE
71 if redirect_stderr: stderr = subprocess.PIPE
72 # TODO(sosa): gpylint complains about redefining built-in 'input'.
73 # Can we rename this variable?
74 if input: stdin = subprocess.PIPE
David James6db8f522010-09-09 10:49:11 -070075 if isinstance(cmd, basestring):
76 if enter_chroot: cmd = './enter_chroot.sh -- ' + cmd
77 cmd_str = cmd
78 else:
79 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
80 cmd_str = ' '.join(cmd)
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070081
82 # Print out the command before running.
83 if print_cmd:
David James6db8f522010-09-09 10:49:11 -070084 Info('RunCommand: %s' % cmd_str)
Doug Andersona8d22de2011-01-13 16:22:58 -080085 cmd_result.cmd = cmd
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070086
87 try:
David James9102a892010-12-02 10:21:49 -080088 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout,
Doug Anderson6781f942011-01-14 16:21:39 -080089 stderr=stderr, shell=shell, env=env)
90 if ignore_sigint:
91 old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
92 try:
93 (cmd_result.output, cmd_result.error) = proc.communicate(input)
94 finally:
95 if ignore_sigint:
96 signal.signal(signal.SIGINT, old_sigint)
97
Scott Zawalski6bc41ac2010-09-08 12:47:28 -070098 if exit_code:
Tan Gao2f310882010-09-10 14:50:47 -070099 cmd_result.returncode = proc.returncode
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700100
101 if not error_ok and proc.returncode:
Tan Gao2f310882010-09-10 14:50:47 -0700102 msg = ('Command "%s" failed.\n' % cmd_str +
103 (error_message or cmd_result.error or cmd_result.output or ''))
104 raise RunCommandError(msg)
Tan Gao2990a4d2010-09-22 09:34:27 -0700105 # TODO(sosa): is it possible not to use the catch-all Exception here?
106 except Exception, e:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700107 if not error_ok:
108 raise
109 else:
110 Warning(str(e))
111
Tan Gao2f310882010-09-10 14:50:47 -0700112 return cmd_result
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700113
114
115class Color(object):
116 """Conditionally wraps text in ANSI color escape sequences."""
117 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
118 BOLD = -1
119 COLOR_START = '\033[1;%dm'
120 BOLD_START = '\033[1m'
121 RESET = '\033[0m'
122
123 def __init__(self, enabled=True):
124 self._enabled = enabled
125
126 def Color(self, color, text):
127 """Returns text with conditionally added color escape sequences.
128
Tan Gao2990a4d2010-09-22 09:34:27 -0700129 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700130 color: Text color -- one of the color constants defined in this class.
131 text: The text to color.
132
133 Returns:
134 If self._enabled is False, returns the original text. If it's True,
135 returns text with color escape sequences based on the value of color.
136 """
137 if not self._enabled:
138 return text
139 if color == self.BOLD:
140 start = self.BOLD_START
141 else:
142 start = self.COLOR_START % (color + 30)
143 return start + text + self.RESET
144
145
146def Die(message):
147 """Emits a red error message and halts execution.
148
Tan Gao2990a4d2010-09-22 09:34:27 -0700149 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700150 message: The message to be emitted before exiting.
151 """
152 print >> sys.stderr, (
153 Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message))
154 sys.exit(1)
155
156
Tan Gao2990a4d2010-09-22 09:34:27 -0700157# pylint: disable-msg=W0622
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700158def Warning(message):
159 """Emits a yellow warning message and continues execution.
160
Tan Gao2990a4d2010-09-22 09:34:27 -0700161 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700162 message: The message to be emitted.
163 """
164 print >> sys.stderr, (
165 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message))
166
167
168def Info(message):
169 """Emits a blue informational message and continues execution.
170
Tan Gao2990a4d2010-09-22 09:34:27 -0700171 Args:
Scott Zawalski6bc41ac2010-09-08 12:47:28 -0700172 message: The message to be emitted.
173 """
174 print >> sys.stderr, (
175 Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message))
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700176
177
178def ListFiles(base_dir):
179 """Recurively list files in a directory.
180
Tan Gao2990a4d2010-09-22 09:34:27 -0700181 Args:
Scott Zawalski98ac6b22010-09-08 15:59:23 -0700182 base_dir: directory to start recursively listing in.
183
184 Returns:
185 A list of files relative to the base_dir path or
186 An empty list of there are no files in the directories.
187 """
188 directories = [base_dir]
189 files_list = []
190 while directories:
191 directory = directories.pop()
192 for name in os.listdir(directory):
193 fullpath = os.path.join(directory, name)
194 if os.path.isfile(fullpath):
195 files_list.append(fullpath)
196 elif os.path.isdir(fullpath):
197 directories.append(fullpath)
198
199 return files_list
Tan Gao2990a4d2010-09-22 09:34:27 -0700200
201
202def IsInsideChroot():
203 """Returns True if we are inside chroot."""
204 return os.path.exists('/etc/debian_chroot')
205
206
207def GetSrcRoot():
208 """Get absolute path to src/scripts/ directory.
209
210 Assuming test script will always be run from descendent of src/scripts.
211
212 Returns:
213 A string, absolute path to src/scripts directory. None if not found.
214 """
215 src_root = None
216 match_str = '/src/scripts/'
217 test_script_path = os.path.abspath('.')
218
219 path_list = re.split(match_str, test_script_path)
220 if path_list:
221 src_root = os.path.join(path_list[0], match_str.strip('/'))
222 Info ('src_root = %r' % src_root)
223 else:
224 Info ('No %r found in %r' % (match_str, test_script_path))
225
226 return src_root
227
228
229def GetChromeosVersion(str_obj):
230 """Helper method to parse output for CHROMEOS_VERSION_STRING.
231
232 Args:
233 str_obj: a string, which may contain Chrome OS version info.
234
235 Returns:
236 A string, value of CHROMEOS_VERSION_STRING environment variable set by
237 chromeos_version.sh. Or None if not found.
238 """
239 if str_obj is not None:
240 match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj)
241 if match and match.group(1):
242 Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1))
243 return match.group(1)
244
245 Info ('CHROMEOS_VERSION_STRING NOT found')
246 return None
247
248
249def GetOutputImageDir(board, cros_version):
250 """Construct absolute path to output image directory.
251
252 Args:
253 board: a string.
254 cros_version: a string, Chrome OS version.
255
256 Returns:
257 a string: absolute path to output directory.
258 """
259 src_root = GetSrcRoot()
260 rel_path = 'build/images/%s' % board
261 # ASSUME: --build_attempt always sets to 1
262 version_str = '-'.join([cros_version, 'a1'])
263 output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str)
264 Info ('output_dir = %s' % output_dir)
265 return output_dir