Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 1 | # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 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 | |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 7 | import inspect |
Scott Zawalski | 98ac6b2 | 2010-09-08 15:59:23 -0700 | [diff] [blame] | 8 | import os |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 9 | import re |
Doug Anderson | 6781f94 | 2011-01-14 16:21:39 -0800 | [diff] [blame] | 10 | import signal |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 11 | import subprocess |
| 12 | import sys |
David James | eeefb85 | 2011-05-19 21:32:38 -0700 | [diff] [blame] | 13 | import time |
Simon Glass | 53ed230 | 2011-02-08 18:42:16 -0800 | [diff] [blame] | 14 | from terminal import Color |
| 15 | |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 16 | |
| 17 | _STDOUT_IS_TTY = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() |
| 18 | |
David James | eeefb85 | 2011-05-19 21:32:38 -0700 | [diff] [blame] | 19 | class GitPushFailed(Exception): |
| 20 | """Raised when a git push failed after retry.""" |
| 21 | pass |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 22 | |
| 23 | class CommandResult(object): |
| 24 | """An object to store various attributes of a child process.""" |
| 25 | |
| 26 | def __init__(self): |
| 27 | self.cmd = None |
| 28 | self.error = None |
| 29 | self.output = None |
| 30 | self.returncode = None |
| 31 | |
| 32 | |
| 33 | class RunCommandError(Exception): |
| 34 | """Error caught in RunCommand() method.""" |
Don Garrett | b85946a | 2011-03-10 18:11:08 -0800 | [diff] [blame] | 35 | def __init__(self, msg, cmd): |
| 36 | self.cmd = cmd |
| 37 | Exception.__init__(self, msg) |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 38 | |
Don Garrett | b85946a | 2011-03-10 18:11:08 -0800 | [diff] [blame] | 39 | def __eq__(self, other): |
| 40 | return (type(self) == type(other) and |
| 41 | str(self) == str(other) and |
| 42 | self.cmd == other.cmd) |
| 43 | |
| 44 | def __ne__(self, other): |
| 45 | return not self.__eq__(other) |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 46 | |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 47 | def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None, |
| 48 | exit_code=False, redirect_stdout=False, redirect_stderr=False, |
Doug Anderson | 6781f94 | 2011-01-14 16:21:39 -0800 | [diff] [blame] | 49 | cwd=None, input=None, enter_chroot=False, shell=False, |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 50 | env=None, extra_env=None, ignore_sigint=False, |
| 51 | combine_stdout_stderr=False): |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 52 | """Runs a command. |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 53 | |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 54 | Args: |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 55 | cmd: cmd to run. Should be input to subprocess.Popen. If a string, shell |
| 56 | must be true. Otherwise the command must be an array of arguments, and |
| 57 | shell must be false. |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 58 | print_cmd: prints the command before running it. |
| 59 | error_ok: does not raise an exception on error. |
| 60 | error_message: prints out this message when an error occurrs. |
| 61 | exit_code: returns the return code of the shell command. |
| 62 | redirect_stdout: returns the stdout. |
| 63 | redirect_stderr: holds stderr output until input is communicated. |
| 64 | cwd: the working directory to run this cmd. |
| 65 | input: input to pipe into this command through stdin. |
| 66 | enter_chroot: this command should be run from within the chroot. If set, |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 67 | cwd must point to the scripts directory. |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 68 | shell: Controls whether we add a shell as a command interpreter. See cmd |
| 69 | since it has to agree as to the type. |
| 70 | env: If non-None, this is the environment for the new process. If |
| 71 | enter_chroot is true then this is the environment of the enter_chroot, |
| 72 | most of which gets removed from the cmd run. |
| 73 | extra_env: If set, this is added to the environment for the new process. |
| 74 | In enter_chroot=True case, these are specified on the post-entry |
| 75 | side, and so are often more useful. This dictionary is not used to |
| 76 | clear any entries though. |
Doug Anderson | 6781f94 | 2011-01-14 16:21:39 -0800 | [diff] [blame] | 77 | ignore_sigint: If True, we'll ignore signal.SIGINT before calling the |
| 78 | child. This is the desired behavior if we know our child will handle |
| 79 | Ctrl-C. If we don't do this, I think we and the child will both get |
| 80 | Ctrl-C at the same time, which means we'll forcefully kill the child. |
Chris Sosa | 66c8c25 | 2011-02-17 11:44:09 -0800 | [diff] [blame] | 81 | combine_stdout_stderr: Combines stdout and stdin streams into stdout. |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 82 | |
| 83 | Returns: |
| 84 | A CommandResult object. |
| 85 | |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 86 | Raises: |
| 87 | Exception: Raises generic exception on error with optional error_message. |
| 88 | """ |
| 89 | # Set default for variables. |
| 90 | stdout = None |
| 91 | stderr = None |
| 92 | stdin = None |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 93 | cmd_result = CommandResult() |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 94 | |
| 95 | # Modify defaults based on parameters. |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 96 | if redirect_stdout: stdout = subprocess.PIPE |
| 97 | if redirect_stderr: stderr = subprocess.PIPE |
Chris Sosa | 66c8c25 | 2011-02-17 11:44:09 -0800 | [diff] [blame] | 98 | if combine_stdout_stderr: stderr = subprocess.STDOUT |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 99 | # TODO(sosa): gpylint complains about redefining built-in 'input'. |
| 100 | # Can we rename this variable? |
| 101 | if input: stdin = subprocess.PIPE |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 102 | |
David James | 6db8f52 | 2010-09-09 10:49:11 -0700 | [diff] [blame] | 103 | if isinstance(cmd, basestring): |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 104 | if not shell: |
| 105 | raise Exception('Cannot run a string command without a shell') |
| 106 | cmd = ['/bin/sh', '-c', cmd] |
| 107 | shell = False |
| 108 | elif shell: |
| 109 | raise Exception('Cannot run an array command with a shell') |
| 110 | |
| 111 | # If we are using enter_chroot we need to use enterchroot pass env through |
| 112 | # to the final command. |
| 113 | if enter_chroot: |
| 114 | cmd = ['./enter_chroot.sh', '--'] + cmd |
| 115 | if extra_env: |
| 116 | for (key, value) in extra_env.items(): |
| 117 | cmd.insert(1, '%s=%s' % (key, value)) |
| 118 | elif extra_env: |
| 119 | if env is not None: |
| 120 | env = env.copy() |
| 121 | else: |
| 122 | env = os.environ.copy() |
| 123 | |
| 124 | env.update(extra_env) |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 125 | |
| 126 | # Print out the command before running. |
| 127 | if print_cmd: |
Don Garrett | f3eac24 | 2011-04-13 17:50:20 -0700 | [diff] [blame] | 128 | if cwd: |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 129 | Info('RunCommand: %r in %s' % (cmd, cwd)) |
Don Garrett | f3eac24 | 2011-04-13 17:50:20 -0700 | [diff] [blame] | 130 | else: |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 131 | Info('RunCommand: %r' % cmd) |
Doug Anderson | a8d22de | 2011-01-13 16:22:58 -0800 | [diff] [blame] | 132 | cmd_result.cmd = cmd |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 133 | |
| 134 | try: |
David James | 9102a89 | 2010-12-02 10:21:49 -0800 | [diff] [blame] | 135 | proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout, |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 136 | stderr=stderr, shell=False, env=env) |
Doug Anderson | 6781f94 | 2011-01-14 16:21:39 -0800 | [diff] [blame] | 137 | if ignore_sigint: |
| 138 | old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN) |
| 139 | try: |
| 140 | (cmd_result.output, cmd_result.error) = proc.communicate(input) |
| 141 | finally: |
| 142 | if ignore_sigint: |
| 143 | signal.signal(signal.SIGINT, old_sigint) |
| 144 | |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 145 | if exit_code: |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 146 | cmd_result.returncode = proc.returncode |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 147 | |
| 148 | if not error_ok and proc.returncode: |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 149 | msg = ('Command "%r" failed.\n' % cmd + |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 150 | (error_message or cmd_result.error or cmd_result.output or '')) |
Don Garrett | b85946a | 2011-03-10 18:11:08 -0800 | [diff] [blame] | 151 | raise RunCommandError(msg, cmd) |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 152 | # TODO(sosa): is it possible not to use the catch-all Exception here? |
Peter Mayo | 193f68f | 2011-04-19 19:08:21 -0400 | [diff] [blame] | 153 | except OSError, e: |
| 154 | if not error_ok: |
| 155 | raise RunCommandError(str(e), cmd) |
| 156 | else: |
| 157 | Warning(str(e)) |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 158 | except Exception, e: |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 159 | if not error_ok: |
| 160 | raise |
| 161 | else: |
| 162 | Warning(str(e)) |
| 163 | |
Tan Gao | 2f31088 | 2010-09-10 14:50:47 -0700 | [diff] [blame] | 164 | return cmd_result |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 165 | |
| 166 | |
Simon Glass | 5329b93 | 2011-03-14 16:49:04 -0700 | [diff] [blame] | 167 | #TODO(sjg): Remove this in favor of operation.Die |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 168 | def Die(message): |
| 169 | """Emits a red error message and halts execution. |
| 170 | |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 171 | Args: |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 172 | message: The message to be emitted before exiting. |
| 173 | """ |
| 174 | print >> sys.stderr, ( |
| 175 | Color(_STDOUT_IS_TTY).Color(Color.RED, '\nERROR: ' + message)) |
| 176 | sys.exit(1) |
| 177 | |
| 178 | |
Simon Glass | 5329b93 | 2011-03-14 16:49:04 -0700 | [diff] [blame] | 179 | #TODO(sjg): Remove this in favor of operation.Warning |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 180 | # pylint: disable-msg=W0622 |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 181 | def Warning(message): |
| 182 | """Emits a yellow warning message and continues execution. |
| 183 | |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 184 | Args: |
Scott Zawalski | 6bc41ac | 2010-09-08 12:47:28 -0700 | [diff] [blame] | 185 | message: The message to be emitted. |
| 186 | """ |
| 187 | print >> sys.stderr, ( |
| 188 | Color(_STDOUT_IS_TTY).Color(Color.YELLOW, '\nWARNING: ' + message)) |
| 189 | |
| 190 | |
Simon Glass | 5329b93 | 2011-03-14 16:49:04 -0700 | [diff] [blame] | 191 | # This command is deprecated in favor of operation.Info() |
| 192 | # It is left here for the moment so people are aware what happened. |
| 193 | # The reason is that this is not aware of the terminal output restrictions such |
| 194 | # as verbose, quiet and subprocess output. You should not be calling this. |
Simon Glass | 6b069ef | 2011-03-14 17:24:10 -0700 | [diff] [blame] | 195 | def Info(message): |
| 196 | """Emits a blue informational message and continues execution. |
| 197 | |
| 198 | Args: |
| 199 | message: The message to be emitted. |
| 200 | """ |
| 201 | print >> sys.stderr, ( |
| 202 | Color(_STDOUT_IS_TTY).Color(Color.BLUE, '\nINFO: ' + message)) |
Scott Zawalski | 98ac6b2 | 2010-09-08 15:59:23 -0700 | [diff] [blame] | 203 | |
| 204 | |
| 205 | def ListFiles(base_dir): |
| 206 | """Recurively list files in a directory. |
| 207 | |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 208 | Args: |
Scott Zawalski | 98ac6b2 | 2010-09-08 15:59:23 -0700 | [diff] [blame] | 209 | base_dir: directory to start recursively listing in. |
| 210 | |
| 211 | Returns: |
| 212 | A list of files relative to the base_dir path or |
| 213 | An empty list of there are no files in the directories. |
| 214 | """ |
| 215 | directories = [base_dir] |
| 216 | files_list = [] |
| 217 | while directories: |
| 218 | directory = directories.pop() |
| 219 | for name in os.listdir(directory): |
| 220 | fullpath = os.path.join(directory, name) |
| 221 | if os.path.isfile(fullpath): |
| 222 | files_list.append(fullpath) |
| 223 | elif os.path.isdir(fullpath): |
| 224 | directories.append(fullpath) |
| 225 | |
| 226 | return files_list |
Tan Gao | 2990a4d | 2010-09-22 09:34:27 -0700 | [diff] [blame] | 227 | |
| 228 | |
| 229 | def IsInsideChroot(): |
| 230 | """Returns True if we are inside chroot.""" |
| 231 | return os.path.exists('/etc/debian_chroot') |
| 232 | |
| 233 | |
| 234 | def GetSrcRoot(): |
| 235 | """Get absolute path to src/scripts/ directory. |
| 236 | |
| 237 | Assuming test script will always be run from descendent of src/scripts. |
| 238 | |
| 239 | Returns: |
| 240 | A string, absolute path to src/scripts directory. None if not found. |
| 241 | """ |
| 242 | src_root = None |
| 243 | match_str = '/src/scripts/' |
| 244 | test_script_path = os.path.abspath('.') |
| 245 | |
| 246 | path_list = re.split(match_str, test_script_path) |
| 247 | if path_list: |
| 248 | src_root = os.path.join(path_list[0], match_str.strip('/')) |
| 249 | Info ('src_root = %r' % src_root) |
| 250 | else: |
| 251 | Info ('No %r found in %r' % (match_str, test_script_path)) |
| 252 | |
| 253 | return src_root |
| 254 | |
| 255 | |
| 256 | def GetChromeosVersion(str_obj): |
| 257 | """Helper method to parse output for CHROMEOS_VERSION_STRING. |
| 258 | |
| 259 | Args: |
| 260 | str_obj: a string, which may contain Chrome OS version info. |
| 261 | |
| 262 | Returns: |
| 263 | A string, value of CHROMEOS_VERSION_STRING environment variable set by |
| 264 | chromeos_version.sh. Or None if not found. |
| 265 | """ |
| 266 | if str_obj is not None: |
| 267 | match = re.search('CHROMEOS_VERSION_STRING=([0-9_.]+)', str_obj) |
| 268 | if match and match.group(1): |
| 269 | Info ('CHROMEOS_VERSION_STRING = %s' % match.group(1)) |
| 270 | return match.group(1) |
| 271 | |
| 272 | Info ('CHROMEOS_VERSION_STRING NOT found') |
| 273 | return None |
| 274 | |
| 275 | |
| 276 | def GetOutputImageDir(board, cros_version): |
| 277 | """Construct absolute path to output image directory. |
| 278 | |
| 279 | Args: |
| 280 | board: a string. |
| 281 | cros_version: a string, Chrome OS version. |
| 282 | |
| 283 | Returns: |
| 284 | a string: absolute path to output directory. |
| 285 | """ |
| 286 | src_root = GetSrcRoot() |
| 287 | rel_path = 'build/images/%s' % board |
| 288 | # ASSUME: --build_attempt always sets to 1 |
| 289 | version_str = '-'.join([cros_version, 'a1']) |
| 290 | output_dir = os.path.join(os.path.dirname(src_root), rel_path, version_str) |
| 291 | Info ('output_dir = %s' % output_dir) |
| 292 | return output_dir |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 293 | |
| 294 | |
| 295 | def FindRepoDir(path=None): |
| 296 | """Returns the nearest higher-level repo dir from the specified path. |
| 297 | |
| 298 | Args: |
| 299 | path: The path to use. Defaults to cwd. |
| 300 | """ |
| 301 | if path is None: |
| 302 | path = os.getcwd() |
| 303 | path = os.path.abspath(path) |
| 304 | while path != '/': |
| 305 | repo_dir = os.path.join(path, '.repo') |
| 306 | if os.path.isdir(repo_dir): |
| 307 | return repo_dir |
| 308 | path = os.path.dirname(path) |
| 309 | return None |
| 310 | |
| 311 | |
| 312 | def ReinterpretPathForChroot(path): |
| 313 | """Returns reinterpreted path from outside the chroot for use inside. |
| 314 | |
| 315 | Args: |
| 316 | path: The path to reinterpret. Must be in src tree. |
| 317 | """ |
| 318 | root_path = os.path.join(FindRepoDir(path), '..') |
| 319 | |
| 320 | path_abs_path = os.path.abspath(path) |
| 321 | root_abs_path = os.path.abspath(root_path) |
| 322 | |
| 323 | # Strip the repository root from the path and strip first /. |
| 324 | relative_path = path_abs_path.replace(root_abs_path, '')[1:] |
| 325 | |
| 326 | if relative_path == path_abs_path: |
| 327 | raise Exception('Error: path is outside your src tree, cannot reinterpret.') |
| 328 | |
| 329 | new_path = os.path.join('/home', os.getenv('USER'), 'trunk', relative_path) |
| 330 | return new_path |
| 331 | |
| 332 | |
David James | eeefb85 | 2011-05-19 21:32:38 -0700 | [diff] [blame] | 333 | def GetPushBranch(branch, cwd): |
| 334 | """Gets the appropriate push branch for the specified branch / directory. |
| 335 | |
| 336 | If branch has a valid tracking branch, we should push to that branch. If |
| 337 | the tracking branch is a revision, we can't push to that, so we should look |
| 338 | at the default branch from the manifest. |
| 339 | |
| 340 | Args: |
| 341 | branch: Branch to examine for tracking branch. |
| 342 | cwd: Directory to look in. |
| 343 | """ |
| 344 | info = {} |
| 345 | for key in ('remote', 'merge'): |
| 346 | cmd = ['git', 'config', 'branch.%s.%s' % (branch, key)] |
| 347 | info[key] = RunCommand(cmd, redirect_stdout=True, cwd=cwd).output.strip() |
| 348 | if not info['merge'].startswith('refs/heads/'): |
| 349 | output = RunCommand(['repo', 'manifest', '-o', '-'], redirect_stdout=True, |
| 350 | cwd=cwd).output |
| 351 | m = re.search(r'<default[^>]*revision="(refs/heads/[^"]*)"', output) |
| 352 | assert m |
| 353 | info['merge'] = m.group(1) |
| 354 | assert info['merge'].startswith('refs/heads/') |
| 355 | return info['remote'], info['merge'].replace('refs/heads/', '') |
| 356 | |
| 357 | |
| 358 | def GitPushWithRetry(branch, cwd, dryrun=False, retries=5): |
| 359 | """General method to push local git changes. |
| 360 | |
| 361 | Args: |
| 362 | branch: Local branch to push. Branch should have already been created |
| 363 | with a local change committed ready to push to the remote branch. Must |
| 364 | also already be checked out to that branch. |
| 365 | cwd: Directory to push in. |
| 366 | dryrun: Git push --dry-run if set to True. |
| 367 | retries: The number of times to retry before giving up, default: 5 |
| 368 | |
| 369 | Raises: |
| 370 | GitPushFailed if push was unsuccessful after retries |
| 371 | """ |
| 372 | remote, push_branch = GetPushBranch(branch, cwd) |
| 373 | for retry in range(1, retries + 1): |
| 374 | try: |
| 375 | RunCommand(['git', 'remote', 'update'], cwd=cwd) |
David James | e77bd56 | 2011-06-08 11:57:38 -0700 | [diff] [blame] | 376 | try: |
| 377 | RunCommand(['git', 'rebase', '%s/%s' % (remote, push_branch)], cwd=cwd) |
| 378 | except RunCommandError: |
| 379 | # Looks like our change conflicts with upstream. Cleanup our failed |
| 380 | # rebase. |
| 381 | RunCommand(['git', 'rebase', '--abort'], error_ok=True, cwd=cwd) |
| 382 | raise |
David James | eeefb85 | 2011-05-19 21:32:38 -0700 | [diff] [blame] | 383 | push_command = ['git', 'push', remote, '%s:%s' % (branch, push_branch)] |
| 384 | if dryrun: |
| 385 | push_command.append('--dry-run') |
| 386 | |
| 387 | RunCommand(push_command, cwd=cwd) |
| 388 | break |
| 389 | except RunCommandError: |
| 390 | if retry < retries: |
| 391 | print 'Error pushing changes trying again (%s/%s)' % (retry, retries) |
| 392 | time.sleep(5 * retry) |
| 393 | else: |
| 394 | raise GitPushFailed('Failed to push change after %s retries' % retries) |
| 395 | |
| 396 | |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 397 | def GetCallerName(): |
| 398 | """Returns the name of the calling module with __main__.""" |
| 399 | top_frame = inspect.stack()[-1][0] |
| 400 | return os.path.basename(top_frame.f_code.co_filename) |
| 401 | |
| 402 | |
| 403 | class RunCommandException(Exception): |
| 404 | """Raised when there is an error in OldRunCommand.""" |
Don Garrett | b85946a | 2011-03-10 18:11:08 -0800 | [diff] [blame] | 405 | def __init__(self, msg, cmd): |
| 406 | self.cmd = cmd |
| 407 | Exception.__init__(self, msg) |
| 408 | |
| 409 | def __eq__(self, other): |
| 410 | return (type(self) == type(other) and |
| 411 | str(self) == str(other) and |
| 412 | self.cmd == other.cmd) |
| 413 | |
| 414 | def __ne__(self, other): |
| 415 | return not self.__eq__(other) |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 416 | |
| 417 | |
| 418 | def OldRunCommand(cmd, print_cmd=True, error_ok=False, error_message=None, |
| 419 | exit_code=False, redirect_stdout=False, redirect_stderr=False, |
| 420 | cwd=None, input=None, enter_chroot=False, num_retries=0): |
| 421 | """Legacy run shell command. |
| 422 | |
| 423 | Arguments: |
| 424 | cmd: cmd to run. Should be input to subprocess.POpen. If a string, |
| 425 | converted to an array using split(). |
| 426 | print_cmd: prints the command before running it. |
| 427 | error_ok: does not raise an exception on error. |
| 428 | error_message: prints out this message when an error occurrs. |
| 429 | exit_code: returns the return code of the shell command. |
| 430 | redirect_stdout: returns the stdout. |
| 431 | redirect_stderr: holds stderr output until input is communicated. |
| 432 | cwd: the working directory to run this cmd. |
| 433 | input: input to pipe into this command through stdin. |
| 434 | enter_chroot: this command should be run from within the chroot. If set, |
| 435 | cwd must point to the scripts directory. |
| 436 | num_retries: the number of retries to perform before dying |
| 437 | |
| 438 | Returns: |
| 439 | If exit_code is True, returns the return code of the shell command. |
| 440 | Else returns the output of the shell command. |
| 441 | |
| 442 | Raises: |
| 443 | Exception: Raises RunCommandException on error with optional error_message. |
| 444 | """ |
| 445 | # Set default for variables. |
| 446 | stdout = None |
| 447 | stderr = None |
| 448 | stdin = None |
| 449 | output = '' |
| 450 | |
| 451 | # Modify defaults based on parameters. |
| 452 | if redirect_stdout: stdout = subprocess.PIPE |
| 453 | if redirect_stderr: stderr = subprocess.PIPE |
| 454 | if input: stdin = subprocess.PIPE |
| 455 | if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd |
| 456 | |
| 457 | # Print out the command before running. |
| 458 | if print_cmd: |
| 459 | Info('PROGRAM(%s) -> RunCommand: %r in dir %s' % |
| 460 | (GetCallerName(), cmd, cwd)) |
| 461 | |
| 462 | for retry_count in range(num_retries + 1): |
| 463 | try: |
| 464 | proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, |
| 465 | stdout=stdout, stderr=stderr) |
| 466 | (output, error) = proc.communicate(input) |
| 467 | if exit_code and retry_count == num_retries: |
| 468 | return proc.returncode |
| 469 | |
| 470 | if proc.returncode == 0: |
| 471 | break |
| 472 | |
| 473 | raise RunCommandException('Command "%r" failed.\n' % (cmd) + |
Don Garrett | b85946a | 2011-03-10 18:11:08 -0800 | [diff] [blame] | 474 | (error_message or error or output or ''), |
| 475 | cmd) |
Chris Sosa | 471532a | 2011-02-01 15:10:06 -0800 | [diff] [blame] | 476 | except RunCommandException as e: |
| 477 | if not error_ok and retry_count == num_retries: |
| 478 | raise e |
| 479 | else: |
| 480 | Warning(str(e)) |
| 481 | if print_cmd: |
| 482 | Info('PROGRAM(%s) -> RunCommand: retrying %r in dir %s' % |
| 483 | (GetCallerName(), cmd, cwd)) |
| 484 | |
| 485 | return output |