OLD | NEW |
| (Empty) |
1 # 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 | |
7 import os | |
8 import re | |
9 import subprocess | |
10 import sys | |
11 | |
12 _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() | |
13 | |
14 | |
15 class 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 | |
25 class RunCommandError(Exception): | |
26 """Error caught in RunCommand() method.""" | |
27 pass | |
28 | |
29 | |
30 def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None, | |
31 exit_code=False, redirect_stdout=False, redirect_stderr=False, | |
32 cwd=None, input=None, enter_chroot=False, shell=False): | |
33 """Runs a command. | |
34 | |
35 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, | |
46 cwd must point to the scripts directory. | |
47 shell: If shell is True, the specified command will be executed through | |
48 the shell. | |
49 | |
50 Returns: | |
51 A CommandResult object. | |
52 | |
53 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 | |
60 cmd_result = CommandResult() | |
61 | |
62 # Modify defaults based on parameters. | |
63 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 | |
68 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) | |
74 | |
75 # Print out the command before running. | |
76 if print_cmd: | |
77 Info('RunCommand: %s' % cmd_str) | |
78 cmd_result.cmd = cmd_str | |
79 | |
80 try: | |
81 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout, | |
82 stderr=stderr, shell=shell) | |
83 (cmd_result.output, cmd_result.error) = proc.communicate(input) | |
84 if exit_code: | |
85 cmd_result.returncode = proc.returncode | |
86 | |
87 if not error_ok and proc.returncode: | |
88 msg = ('Command "%s" failed.\n' % cmd_str + | |
89 (error_message or cmd_result.error or cmd_result.output or '')) | |
90 raise RunCommandError(msg) | |
91 # TODO(sosa): is it possible not to use the catch-all Exception here? | |
92 except Exception, e: | |
93 if not error_ok: | |
94 raise | |
95 else: | |
96 Warning(str(e)) | |
97 | |
98 return cmd_result | |
99 | |
100 | |
101 class 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 | |
115 Args: | |
116 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 | |
132 def Die(message): | |
133 """Emits a red error message and halts execution. | |
134 | |
135 Args: | |
136 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 | |
143 # pylint: disable-msg=W0622 | |
144 def Warning(message): | |
145 """Emits a yellow warning message and continues execution. | |
146 | |
147 Args: | |
148 message: The message to be emitted. | |
149 """ | |
150 print >> sys.stderr, ( | |
151 Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message)) | |
152 | |
153 | |
154 def Info(message): | |
155 """Emits a blue informational message and continues execution. | |
156 | |
157 Args: | |
158 message: The message to be emitted. | |
159 """ | |
160 print >> sys.stderr, ( | |
161 Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message)) | |
162 | |
163 | |
164 def ListFiles(base_dir): | |
165 """Recurively list files in a directory. | |
166 | |
167 Args: | |
168 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 | |
186 | |
187 | |
188 def IsInsideChroot(): | |
189 """Returns True if we are inside chroot.""" | |
190 return os.path.exists('/etc/debian_chroot') | |
191 | |
192 | |
193 def 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 | |
215 def 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 | |
235 def 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 | |
OLD | NEW |