OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2013 the V8 project authors. All rights reserved. | 2 # Copyright 2013 the V8 project authors. All rights reserved. |
3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
5 # met: | 5 # met: |
6 # | 6 # |
7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
10 # copyright notice, this list of conditions and the following | 10 # copyright notice, this list of conditions and the following |
11 # disclaimer in the documentation and/or other materials provided | 11 # disclaimer in the documentation and/or other materials provided |
12 # with the distribution. | 12 # with the distribution. |
13 # * Neither the name of Google Inc. nor the names of its | 13 # * Neither the name of Google Inc. nor the names of its |
14 # contributors may be used to endorse or promote products derived | 14 # contributors may be used to endorse or promote products derived |
15 # from this software without specific prior written permission. | 15 # from this software without specific prior written permission. |
16 # | 16 # |
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | 28 |
29 import datetime | 29 import datetime |
| 30 import json |
30 import os | 31 import os |
31 import re | 32 import re |
32 import subprocess | 33 import subprocess |
33 import sys | 34 import sys |
34 import textwrap | 35 import textwrap |
35 import time | 36 import time |
36 import urllib2 | 37 import urllib2 |
37 | 38 |
38 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" | 39 PERSISTFILE_BASENAME = "PERSISTFILE_BASENAME" |
39 TEMP_BRANCH = "TEMP_BRANCH" | 40 TEMP_BRANCH = "TEMP_BRANCH" |
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
239 self._config = config | 240 self._config = config |
240 self._state = state | 241 self._state = state |
241 self._options = options | 242 self._options = options |
242 self._side_effect_handler = handler | 243 self._side_effect_handler = handler |
243 assert self._number >= 0 | 244 assert self._number >= 0 |
244 assert self._config is not None | 245 assert self._config is not None |
245 assert self._state is not None | 246 assert self._state is not None |
246 assert self._side_effect_handler is not None | 247 assert self._side_effect_handler is not None |
247 assert isinstance(options, CommonOptions) | 248 assert isinstance(options, CommonOptions) |
248 | 249 |
| 250 def __getitem__(self, key): |
| 251 # Convenience method to allow direct [] access on step classes for |
| 252 # manipulating the backed state dict. |
| 253 return self._state[key] |
| 254 |
| 255 def __setitem__(self, key, value): |
| 256 # Convenience method to allow direct [] access on step classes for |
| 257 # manipulating the backed state dict. |
| 258 self._state[key] = value |
| 259 |
249 def Config(self, key): | 260 def Config(self, key): |
250 return self._config[key] | 261 return self._config[key] |
251 | 262 |
252 def Run(self): | 263 def Run(self): |
253 if self._requires: | 264 # Restore state. |
254 self.RestoreIfUnset(self._requires) | 265 state_file = "%s-state.json" % self._config[PERSISTFILE_BASENAME] |
255 if not self._state[self._requires]: | 266 if not self._state and os.path.exists(state_file): |
256 return | 267 self._state.update(json.loads(FileToText(state_file))) |
| 268 |
| 269 # Skip step if requirement is not met. |
| 270 if self._requires and not self._state.get(self._requires): |
| 271 return |
| 272 |
257 print ">>> Step %d: %s" % (self._number, self._text) | 273 print ">>> Step %d: %s" % (self._number, self._text) |
258 self.RunStep() | 274 self.RunStep() |
259 | 275 |
| 276 # Persist state. |
| 277 TextToFile(json.dumps(self._state), state_file) |
| 278 |
260 def RunStep(self): | 279 def RunStep(self): |
261 raise NotImplementedError | 280 raise NotImplementedError |
262 | 281 |
263 def Retry(self, cb, retry_on=None, wait_plan=None): | 282 def Retry(self, cb, retry_on=None, wait_plan=None): |
264 """ Retry a function. | 283 """ Retry a function. |
265 Params: | 284 Params: |
266 cb: The function to retry. | 285 cb: The function to retry. |
267 retry_on: A callback that takes the result of the function and returns | 286 retry_on: A callback that takes the result of the function and returns |
268 True if the function should be retried. A function throwing an | 287 True if the function should be retried. A function throwing an |
269 exception is always retried. | 288 exception is always retried. |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
342 if re.match(r".*\s+%s$" % name, line): | 361 if re.match(r".*\s+%s$" % name, line): |
343 msg = "Branch %s exists, do you want to delete it?" % name | 362 msg = "Branch %s exists, do you want to delete it?" % name |
344 if self.Confirm(msg): | 363 if self.Confirm(msg): |
345 if self.Git("branch -D %s" % name) is None: | 364 if self.Git("branch -D %s" % name) is None: |
346 self.Die("Deleting branch '%s' failed." % name) | 365 self.Die("Deleting branch '%s' failed." % name) |
347 print "Branch %s deleted." % name | 366 print "Branch %s deleted." % name |
348 else: | 367 else: |
349 msg = "Can't continue. Please delete branch %s and try again." % name | 368 msg = "Can't continue. Please delete branch %s and try again." % name |
350 self.Die(msg) | 369 self.Die(msg) |
351 | 370 |
352 def Persist(self, var, value): | |
353 value = value or "__EMPTY__" | |
354 TextToFile(value, "%s-%s" % (self._config[PERSISTFILE_BASENAME], var)) | |
355 | |
356 def Restore(self, var): | |
357 value = FileToText("%s-%s" % (self._config[PERSISTFILE_BASENAME], var)) | |
358 value = value or self.Die("Variable '%s' could not be restored." % var) | |
359 return "" if value == "__EMPTY__" else value | |
360 | |
361 def RestoreIfUnset(self, var_name): | |
362 if self._state.get(var_name) is None: | |
363 self._state[var_name] = self.Restore(var_name) | |
364 | |
365 def InitialEnvironmentChecks(self): | 371 def InitialEnvironmentChecks(self): |
366 # Cancel if this is not a git checkout. | 372 # Cancel if this is not a git checkout. |
367 if not os.path.exists(self._config[DOT_GIT_LOCATION]): | 373 if not os.path.exists(self._config[DOT_GIT_LOCATION]): |
368 self.Die("This is not a git checkout, this script won't work for you.") | 374 self.Die("This is not a git checkout, this script won't work for you.") |
369 | 375 |
370 # Cancel if EDITOR is unset or not executable. | 376 # Cancel if EDITOR is unset or not executable. |
371 if (self._options.requires_editor and (not os.environ.get("EDITOR") or | 377 if (self._options.requires_editor and (not os.environ.get("EDITOR") or |
372 Command("which", os.environ["EDITOR"]) is None)): | 378 Command("which", os.environ["EDITOR"]) is None)): |
373 self.Die("Please set your EDITOR environment variable, you'll need it.") | 379 self.Die("Please set your EDITOR environment variable, you'll need it.") |
374 | 380 |
375 def CommonPrepare(self): | 381 def CommonPrepare(self): |
376 # Check for a clean workdir. | 382 # Check for a clean workdir. |
377 if self.Git("status -s -uno").strip() != "": | 383 if self.Git("status -s -uno").strip() != "": |
378 self.Die("Workspace is not clean. Please commit or undo your changes.") | 384 self.Die("Workspace is not clean. Please commit or undo your changes.") |
379 | 385 |
380 # Persist current branch. | 386 # Persist current branch. |
381 current_branch = "" | 387 self["current_branch"] = "" |
382 git_result = self.Git("status -s -b -uno").strip() | 388 git_result = self.Git("status -s -b -uno").strip() |
383 for line in git_result.splitlines(): | 389 for line in git_result.splitlines(): |
384 match = re.match(r"^## (.+)", line) | 390 match = re.match(r"^## (.+)", line) |
385 if match: | 391 if match: |
386 current_branch = match.group(1) | 392 self["current_branch"] = match.group(1) |
387 break | 393 break |
388 self.Persist("current_branch", current_branch) | |
389 | 394 |
390 # Fetch unfetched revisions. | 395 # Fetch unfetched revisions. |
391 if self.Git("svn fetch") is None: | 396 if self.Git("svn fetch") is None: |
392 self.Die("'git svn fetch' failed.") | 397 self.Die("'git svn fetch' failed.") |
393 | 398 |
394 def PrepareBranch(self): | 399 def PrepareBranch(self): |
395 # Get ahold of a safe temporary branch and check it out. | 400 # Get ahold of a safe temporary branch and check it out. |
396 self.RestoreIfUnset("current_branch") | 401 if self["current_branch"] != self._config[TEMP_BRANCH]: |
397 if self._state["current_branch"] != self._config[TEMP_BRANCH]: | |
398 self.DeleteBranch(self._config[TEMP_BRANCH]) | 402 self.DeleteBranch(self._config[TEMP_BRANCH]) |
399 self.Git("checkout -b %s" % self._config[TEMP_BRANCH]) | 403 self.Git("checkout -b %s" % self._config[TEMP_BRANCH]) |
400 | 404 |
401 # Delete the branch that will be created later if it exists already. | 405 # Delete the branch that will be created later if it exists already. |
402 self.DeleteBranch(self._config[BRANCHNAME]) | 406 self.DeleteBranch(self._config[BRANCHNAME]) |
403 | 407 |
404 def CommonCleanup(self): | 408 def CommonCleanup(self): |
405 self.RestoreIfUnset("current_branch") | 409 self.Git("checkout -f %s" % self["current_branch"]) |
406 self.Git("checkout -f %s" % self._state["current_branch"]) | 410 if self._config[TEMP_BRANCH] != self["current_branch"]: |
407 if self._config[TEMP_BRANCH] != self._state["current_branch"]: | |
408 self.Git("branch -D %s" % self._config[TEMP_BRANCH]) | 411 self.Git("branch -D %s" % self._config[TEMP_BRANCH]) |
409 if self._config[BRANCHNAME] != self._state["current_branch"]: | 412 if self._config[BRANCHNAME] != self["current_branch"]: |
410 self.Git("branch -D %s" % self._config[BRANCHNAME]) | 413 self.Git("branch -D %s" % self._config[BRANCHNAME]) |
411 | 414 |
412 # Clean up all temporary files. | 415 # Clean up all temporary files. |
413 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) | 416 Command("rm", "-f %s*" % self._config[PERSISTFILE_BASENAME]) |
414 | 417 |
415 def ReadAndPersistVersion(self, prefix=""): | 418 def ReadAndPersistVersion(self, prefix=""): |
416 def ReadAndPersist(var_name, def_name): | 419 def ReadAndPersist(var_name, def_name): |
417 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) | 420 match = re.match(r"^#define %s\s+(\d*)" % def_name, line) |
418 if match: | 421 if match: |
419 value = match.group(1) | 422 value = match.group(1) |
420 self.Persist("%s%s" % (prefix, var_name), value) | 423 self["%s%s" % (prefix, var_name)] = value |
421 self._state["%s%s" % (prefix, var_name)] = value | |
422 for line in LinesInFile(self._config[VERSION_FILE]): | 424 for line in LinesInFile(self._config[VERSION_FILE]): |
423 for (var_name, def_name) in [("major", "MAJOR_VERSION"), | 425 for (var_name, def_name) in [("major", "MAJOR_VERSION"), |
424 ("minor", "MINOR_VERSION"), | 426 ("minor", "MINOR_VERSION"), |
425 ("build", "BUILD_NUMBER"), | 427 ("build", "BUILD_NUMBER"), |
426 ("patch", "PATCH_LEVEL")]: | 428 ("patch", "PATCH_LEVEL")]: |
427 ReadAndPersist(var_name, def_name) | 429 ReadAndPersist(var_name, def_name) |
428 | 430 |
429 def RestoreVersionIfUnset(self, prefix=""): | |
430 for v in ["major", "minor", "build", "patch"]: | |
431 self.RestoreIfUnset("%s%s" % (prefix, v)) | |
432 | |
433 def WaitForLGTM(self): | 431 def WaitForLGTM(self): |
434 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " | 432 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit " |
435 "your change. (If you need to iterate on the patch or double check " | 433 "your change. (If you need to iterate on the patch or double check " |
436 "that it's sane, do so in another shell, but remember to not " | 434 "that it's sane, do so in another shell, but remember to not " |
437 "change the headline of the uploaded CL.") | 435 "change the headline of the uploaded CL.") |
438 answer = "" | 436 answer = "" |
439 while answer != "LGTM": | 437 while answer != "LGTM": |
440 print "> ", | 438 print "> ", |
441 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM") | 439 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM") |
442 if answer != "LGTM": | 440 if answer != "LGTM": |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
502 | 500 |
503 return step_class(message, requires, number=number, config=config, | 501 return step_class(message, requires, number=number, config=config, |
504 state=state, options=options, | 502 state=state, options=options, |
505 handler=side_effect_handler) | 503 handler=side_effect_handler) |
506 | 504 |
507 | 505 |
508 def RunScript(step_classes, | 506 def RunScript(step_classes, |
509 config, | 507 config, |
510 options, | 508 options, |
511 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): | 509 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER): |
| 510 state_file = "%s-state.json" % config[PERSISTFILE_BASENAME] |
| 511 if options.s == 0 and os.path.exists(state_file): |
| 512 os.remove(state_file) |
512 state = {} | 513 state = {} |
513 steps = [] | 514 steps = [] |
514 for (number, step_class) in enumerate(step_classes): | 515 for (number, step_class) in enumerate(step_classes): |
515 steps.append(MakeStep(step_class, number, state, config, | 516 steps.append(MakeStep(step_class, number, state, config, |
516 options, side_effect_handler)) | 517 options, side_effect_handler)) |
517 | 518 |
518 for step in steps[options.s:]: | 519 for step in steps[options.s:]: |
519 step.Run() | 520 step.Run() |
OLD | NEW |