Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(52)

Side by Side Diff: tools/push-to-trunk/common_includes.py

Issue 868473004: External name changes of release scripts. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2013 the V8 project authors. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following
11 # disclaimer in the documentation and/or other materials provided
12 # with the distribution.
13 # * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived
15 # from this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
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.
28
29 import argparse
30 import datetime
31 import httplib
32 import glob
33 import imp
34 import json
35 import os
36 import re
37 import shutil
38 import subprocess
39 import sys
40 import textwrap
41 import time
42 import urllib
43 import urllib2
44
45 from git_recipes import GitRecipesMixin
46 from git_recipes import GitFailedException
47
48 CHANGELOG_FILE = "ChangeLog"
49 VERSION_FILE = os.path.join("src", "version.cc")
50
51 # V8 base directory.
52 V8_BASE = os.path.dirname(
53 os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
54
55
56 def TextToFile(text, file_name):
57 with open(file_name, "w") as f:
58 f.write(text)
59
60
61 def AppendToFile(text, file_name):
62 with open(file_name, "a") as f:
63 f.write(text)
64
65
66 def LinesInFile(file_name):
67 with open(file_name) as f:
68 for line in f:
69 yield line
70
71
72 def FileToText(file_name):
73 with open(file_name) as f:
74 return f.read()
75
76
77 def MSub(rexp, replacement, text):
78 return re.sub(rexp, replacement, text, flags=re.MULTILINE)
79
80
81 def Fill80(line):
82 # Replace tabs and remove surrounding space.
83 line = re.sub(r"\t", r" ", line.strip())
84
85 # Format with 8 characters indentation and line width 80.
86 return textwrap.fill(line, width=80, initial_indent=" ",
87 subsequent_indent=" ")
88
89
90 def MakeComment(text):
91 return MSub(r"^( ?)", "#", text)
92
93
94 def StripComments(text):
95 # Use split not splitlines to keep terminal newlines.
96 return "\n".join(filter(lambda x: not x.startswith("#"), text.split("\n")))
97
98
99 def MakeChangeLogBody(commit_messages, auto_format=False):
100 result = ""
101 added_titles = set()
102 for (title, body, author) in commit_messages:
103 # TODO(machenbach): Better check for reverts. A revert should remove the
104 # original CL from the actual log entry.
105 title = title.strip()
106 if auto_format:
107 # Only add commits that set the LOG flag correctly.
108 log_exp = r"^[ \t]*LOG[ \t]*=[ \t]*(?:(?:Y(?:ES)?)|TRUE)"
109 if not re.search(log_exp, body, flags=re.I | re.M):
110 continue
111 # Never include reverts.
112 if title.startswith("Revert "):
113 continue
114 # Don't include duplicates.
115 if title in added_titles:
116 continue
117
118 # Add and format the commit's title and bug reference. Move dot to the end.
119 added_titles.add(title)
120 raw_title = re.sub(r"(\.|\?|!)$", "", title)
121 bug_reference = MakeChangeLogBugReference(body)
122 space = " " if bug_reference else ""
123 result += "%s\n" % Fill80("%s%s%s." % (raw_title, space, bug_reference))
124
125 # Append the commit's author for reference if not in auto-format mode.
126 if not auto_format:
127 result += "%s\n" % Fill80("(%s)" % author.strip())
128
129 result += "\n"
130 return result
131
132
133 def MakeChangeLogBugReference(body):
134 """Grep for "BUG=xxxx" lines in the commit message and convert them to
135 "(issue xxxx)".
136 """
137 crbugs = []
138 v8bugs = []
139
140 def AddIssues(text):
141 ref = re.match(r"^BUG[ \t]*=[ \t]*(.+)$", text.strip())
142 if not ref:
143 return
144 for bug in ref.group(1).split(","):
145 bug = bug.strip()
146 match = re.match(r"^v8:(\d+)$", bug)
147 if match: v8bugs.append(int(match.group(1)))
148 else:
149 match = re.match(r"^(?:chromium:)?(\d+)$", bug)
150 if match: crbugs.append(int(match.group(1)))
151
152 # Add issues to crbugs and v8bugs.
153 map(AddIssues, body.splitlines())
154
155 # Filter duplicates, sort, stringify.
156 crbugs = map(str, sorted(set(crbugs)))
157 v8bugs = map(str, sorted(set(v8bugs)))
158
159 bug_groups = []
160 def FormatIssues(prefix, bugs):
161 if len(bugs) > 0:
162 plural = "s" if len(bugs) > 1 else ""
163 bug_groups.append("%sissue%s %s" % (prefix, plural, ", ".join(bugs)))
164
165 FormatIssues("", v8bugs)
166 FormatIssues("Chromium ", crbugs)
167
168 if len(bug_groups) > 0:
169 return "(%s)" % ", ".join(bug_groups)
170 else:
171 return ""
172
173
174 def SortingKey(version):
175 """Key for sorting version number strings: '3.11' > '3.2.1.1'"""
176 version_keys = map(int, version.split("."))
177 # Fill up to full version numbers to normalize comparison.
178 while len(version_keys) < 4: # pragma: no cover
179 version_keys.append(0)
180 # Fill digits.
181 return ".".join(map("{0:04d}".format, version_keys))
182
183
184 # Some commands don't like the pipe, e.g. calling vi from within the script or
185 # from subscripts like git cl upload.
186 def Command(cmd, args="", prefix="", pipe=True, cwd=None):
187 cwd = cwd or os.getcwd()
188 # TODO(machenbach): Use timeout.
189 cmd_line = "%s %s %s" % (prefix, cmd, args)
190 print "Command: %s" % cmd_line
191 print "in %s" % cwd
192 sys.stdout.flush()
193 try:
194 if pipe:
195 return subprocess.check_output(cmd_line, shell=True, cwd=cwd)
196 else:
197 return subprocess.check_call(cmd_line, shell=True, cwd=cwd)
198 except subprocess.CalledProcessError:
199 return None
200 finally:
201 sys.stdout.flush()
202 sys.stderr.flush()
203
204
205 # Wrapper for side effects.
206 class SideEffectHandler(object): # pragma: no cover
207 def Call(self, fun, *args, **kwargs):
208 return fun(*args, **kwargs)
209
210 def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
211 return Command(cmd, args, prefix, pipe, cwd=cwd)
212
213 def ReadLine(self):
214 return sys.stdin.readline().strip()
215
216 def ReadURL(self, url, params=None):
217 # pylint: disable=E1121
218 url_fh = urllib2.urlopen(url, params, 60)
219 try:
220 return url_fh.read()
221 finally:
222 url_fh.close()
223
224 def ReadClusterFuzzAPI(self, api_key, **params):
225 params["api_key"] = api_key.strip()
226 params = urllib.urlencode(params)
227
228 headers = {"Content-type": "application/x-www-form-urlencoded"}
229
230 conn = httplib.HTTPSConnection("backend-dot-cluster-fuzz.appspot.com")
231 conn.request("POST", "/_api/", params, headers)
232
233 response = conn.getresponse()
234 data = response.read()
235
236 try:
237 return json.loads(data)
238 except:
239 print data
240 print "ERROR: Could not read response. Is your key valid?"
241 raise
242
243 def Sleep(self, seconds):
244 time.sleep(seconds)
245
246 def GetDate(self):
247 return datetime.date.today().strftime("%Y-%m-%d")
248
249 def GetUTCStamp(self):
250 return time.mktime(datetime.datetime.utcnow().timetuple())
251
252 DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler()
253
254
255 class NoRetryException(Exception):
256 pass
257
258
259 class VCInterface(object):
260 def InjectStep(self, step):
261 self.step=step
262
263 def Pull(self):
264 raise NotImplementedError()
265
266 def Fetch(self):
267 raise NotImplementedError()
268
269 def GetTags(self):
270 raise NotImplementedError()
271
272 def GetBranches(self):
273 raise NotImplementedError()
274
275 def MasterBranch(self):
276 raise NotImplementedError()
277
278 def CandidateBranch(self):
279 raise NotImplementedError()
280
281 def RemoteMasterBranch(self):
282 raise NotImplementedError()
283
284 def RemoteCandidateBranch(self):
285 raise NotImplementedError()
286
287 def RemoteBranch(self, name):
288 raise NotImplementedError()
289
290 def CLLand(self):
291 raise NotImplementedError()
292
293 def Tag(self, tag, remote, message):
294 """Sets a tag for the current commit.
295
296 Assumptions: The commit already landed and the commit message is unique.
297 """
298 raise NotImplementedError()
299
300
301 class GitInterface(VCInterface):
302 def Pull(self):
303 self.step.GitPull()
304
305 def Fetch(self):
306 self.step.Git("fetch")
307
308 def GetTags(self):
309 return self.step.Git("tag").strip().splitlines()
310
311 def GetBranches(self):
312 # Get relevant remote branches, e.g. "branch-heads/3.25".
313 branches = filter(
314 lambda s: re.match(r"^branch\-heads/\d+\.\d+$", s),
315 self.step.GitRemotes())
316 # Remove 'branch-heads/' prefix.
317 return map(lambda s: s[13:], branches)
318
319 def MasterBranch(self):
320 return "master"
321
322 def CandidateBranch(self):
323 return "candidates"
324
325 def RemoteMasterBranch(self):
326 return "origin/master"
327
328 def RemoteCandidateBranch(self):
329 return "origin/candidates"
330
331 def RemoteBranch(self, name):
332 if name in ["candidates", "master"]:
333 return "origin/%s" % name
334 return "branch-heads/%s" % name
335
336 def Tag(self, tag, remote, message):
337 # Wait for the commit to appear. Assumes unique commit message titles (this
338 # is the case for all automated merge and push commits - also no title is
339 # the prefix of another title).
340 commit = None
341 for wait_interval in [3, 7, 15, 35, 45, 60]:
342 self.step.Git("fetch")
343 commit = self.step.GitLog(n=1, format="%H", grep=message, branch=remote)
344 if commit:
345 break
346 print("The commit has not replicated to git. Waiting for %s seconds." %
347 wait_interval)
348 self.step._side_effect_handler.Sleep(wait_interval)
349 else:
350 self.step.Die("Couldn't determine commit for setting the tag. Maybe the "
351 "git updater is lagging behind?")
352
353 self.step.Git("tag %s %s" % (tag, commit))
354 self.step.Git("push origin %s" % tag)
355
356 def CLLand(self):
357 self.step.GitCLLand()
358
359
360 class Step(GitRecipesMixin):
361 def __init__(self, text, number, config, state, options, handler):
362 self._text = text
363 self._number = number
364 self._config = config
365 self._state = state
366 self._options = options
367 self._side_effect_handler = handler
368 self.vc = GitInterface()
369 self.vc.InjectStep(self)
370
371 # The testing configuration might set a different default cwd.
372 self.default_cwd = (self._config.get("DEFAULT_CWD") or
373 os.path.join(self._options.work_dir, "v8"))
374
375 assert self._number >= 0
376 assert self._config is not None
377 assert self._state is not None
378 assert self._side_effect_handler is not None
379
380 def __getitem__(self, key):
381 # Convenience method to allow direct [] access on step classes for
382 # manipulating the backed state dict.
383 return self._state[key]
384
385 def __setitem__(self, key, value):
386 # Convenience method to allow direct [] access on step classes for
387 # manipulating the backed state dict.
388 self._state[key] = value
389
390 def Config(self, key):
391 return self._config[key]
392
393 def Run(self):
394 # Restore state.
395 state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
396 if not self._state and os.path.exists(state_file):
397 self._state.update(json.loads(FileToText(state_file)))
398
399 print ">>> Step %d: %s" % (self._number, self._text)
400 try:
401 return self.RunStep()
402 finally:
403 # Persist state.
404 TextToFile(json.dumps(self._state), state_file)
405
406 def RunStep(self): # pragma: no cover
407 raise NotImplementedError
408
409 def Retry(self, cb, retry_on=None, wait_plan=None):
410 """ Retry a function.
411 Params:
412 cb: The function to retry.
413 retry_on: A callback that takes the result of the function and returns
414 True if the function should be retried. A function throwing an
415 exception is always retried.
416 wait_plan: A list of waiting delays between retries in seconds. The
417 maximum number of retries is len(wait_plan).
418 """
419 retry_on = retry_on or (lambda x: False)
420 wait_plan = list(wait_plan or [])
421 wait_plan.reverse()
422 while True:
423 got_exception = False
424 try:
425 result = cb()
426 except NoRetryException as e:
427 raise e
428 except Exception as e:
429 got_exception = e
430 if got_exception or retry_on(result):
431 if not wait_plan: # pragma: no cover
432 raise Exception("Retried too often. Giving up. Reason: %s" %
433 str(got_exception))
434 wait_time = wait_plan.pop()
435 print "Waiting for %f seconds." % wait_time
436 self._side_effect_handler.Sleep(wait_time)
437 print "Retrying..."
438 else:
439 return result
440
441 def ReadLine(self, default=None):
442 # Don't prompt in forced mode.
443 if self._options.force_readline_defaults and default is not None:
444 print "%s (forced)" % default
445 return default
446 else:
447 return self._side_effect_handler.ReadLine()
448
449 def Command(self, name, args, cwd=None):
450 cmd = lambda: self._side_effect_handler.Command(
451 name, args, "", True, cwd=cwd or self.default_cwd)
452 return self.Retry(cmd, None, [5])
453
454 def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
455 cmd = lambda: self._side_effect_handler.Command(
456 "git", args, prefix, pipe, cwd=cwd or self.default_cwd)
457 result = self.Retry(cmd, retry_on, [5, 30])
458 if result is None:
459 raise GitFailedException("'git %s' failed." % args)
460 return result
461
462 def Editor(self, args):
463 if self._options.requires_editor:
464 return self._side_effect_handler.Command(
465 os.environ["EDITOR"],
466 args,
467 pipe=False,
468 cwd=self.default_cwd)
469
470 def ReadURL(self, url, params=None, retry_on=None, wait_plan=None):
471 wait_plan = wait_plan or [3, 60, 600]
472 cmd = lambda: self._side_effect_handler.ReadURL(url, params)
473 return self.Retry(cmd, retry_on, wait_plan)
474
475 def GetDate(self):
476 return self._side_effect_handler.GetDate()
477
478 def Die(self, msg=""):
479 if msg != "":
480 print "Error: %s" % msg
481 print "Exiting"
482 raise Exception(msg)
483
484 def DieNoManualMode(self, msg=""):
485 if not self._options.manual: # pragma: no cover
486 msg = msg or "Only available in manual mode."
487 self.Die(msg)
488
489 def Confirm(self, msg):
490 print "%s [Y/n] " % msg,
491 answer = self.ReadLine(default="Y")
492 return answer == "" or answer == "Y" or answer == "y"
493
494 def DeleteBranch(self, name):
495 for line in self.GitBranch().splitlines():
496 if re.match(r"\*?\s*%s$" % re.escape(name), line):
497 msg = "Branch %s exists, do you want to delete it?" % name
498 if self.Confirm(msg):
499 self.GitDeleteBranch(name)
500 print "Branch %s deleted." % name
501 else:
502 msg = "Can't continue. Please delete branch %s and try again." % name
503 self.Die(msg)
504
505 def InitialEnvironmentChecks(self, cwd):
506 # Cancel if this is not a git checkout.
507 if not os.path.exists(os.path.join(cwd, ".git")): # pragma: no cover
508 self.Die("This is not a git checkout, this script won't work for you.")
509
510 # Cancel if EDITOR is unset or not executable.
511 if (self._options.requires_editor and (not os.environ.get("EDITOR") or
512 self.Command(
513 "which", os.environ["EDITOR"]) is None)): # pragma: no cover
514 self.Die("Please set your EDITOR environment variable, you'll need it.")
515
516 def CommonPrepare(self):
517 # Check for a clean workdir.
518 if not self.GitIsWorkdirClean(): # pragma: no cover
519 self.Die("Workspace is not clean. Please commit or undo your changes.")
520
521 # Persist current branch.
522 self["current_branch"] = self.GitCurrentBranch()
523
524 # Fetch unfetched revisions.
525 self.vc.Fetch()
526
527 def PrepareBranch(self):
528 # Delete the branch that will be created later if it exists already.
529 self.DeleteBranch(self._config["BRANCHNAME"])
530
531 def CommonCleanup(self):
532 if ' ' in self["current_branch"]:
533 self.GitCheckout('master')
534 else:
535 self.GitCheckout(self["current_branch"])
536 if self._config["BRANCHNAME"] != self["current_branch"]:
537 self.GitDeleteBranch(self._config["BRANCHNAME"])
538
539 # Clean up all temporary files.
540 for f in glob.iglob("%s*" % self._config["PERSISTFILE_BASENAME"]):
541 if os.path.isfile(f):
542 os.remove(f)
543 if os.path.isdir(f):
544 shutil.rmtree(f)
545
546 def ReadAndPersistVersion(self, prefix=""):
547 def ReadAndPersist(var_name, def_name):
548 match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
549 if match:
550 value = match.group(1)
551 self["%s%s" % (prefix, var_name)] = value
552 for line in LinesInFile(os.path.join(self.default_cwd, VERSION_FILE)):
553 for (var_name, def_name) in [("major", "MAJOR_VERSION"),
554 ("minor", "MINOR_VERSION"),
555 ("build", "BUILD_NUMBER"),
556 ("patch", "PATCH_LEVEL")]:
557 ReadAndPersist(var_name, def_name)
558
559 def WaitForLGTM(self):
560 print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
561 "your change. (If you need to iterate on the patch or double check "
562 "that it's sane, do so in another shell, but remember to not "
563 "change the headline of the uploaded CL.")
564 answer = ""
565 while answer != "LGTM":
566 print "> ",
567 answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
568 if answer != "LGTM":
569 print "That was not 'LGTM'."
570
571 def WaitForResolvingConflicts(self, patch_file):
572 print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
573 "or resolve the conflicts, stage *all* touched files with "
574 "'git add', and type \"RESOLVED<Return>\"")
575 self.DieNoManualMode()
576 answer = ""
577 while answer != "RESOLVED":
578 if answer == "ABORT":
579 self.Die("Applying the patch failed.")
580 if answer != "":
581 print "That was not 'RESOLVED' or 'ABORT'."
582 print "> ",
583 answer = self.ReadLine()
584
585 # Takes a file containing the patch to apply as first argument.
586 def ApplyPatch(self, patch_file, revert=False):
587 try:
588 self.GitApplyPatch(patch_file, revert)
589 except GitFailedException:
590 self.WaitForResolvingConflicts(patch_file)
591
592 def FindLastCandidatesPush(
593 self, parent_hash="", branch="", include_patches=False):
594 push_pattern = "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*"
595 if not include_patches:
596 # Non-patched versions only have three numbers followed by the "(based
597 # on...) comment."
598 push_pattern += " (based"
599 branch = "" if parent_hash else branch or self.vc.RemoteCandidateBranch()
600 return self.GitLog(n=1, format="%H", grep=push_pattern,
601 parent_hash=parent_hash, branch=branch)
602
603 def ArrayToVersion(self, prefix):
604 return ".".join([self[prefix + "major"],
605 self[prefix + "minor"],
606 self[prefix + "build"],
607 self[prefix + "patch"]])
608
609 def StoreVersion(self, version, prefix):
610 version_parts = version.split(".")
611 if len(version_parts) == 3:
612 version_parts.append("0")
613 major, minor, build, patch = version_parts
614 self[prefix + "major"] = major
615 self[prefix + "minor"] = minor
616 self[prefix + "build"] = build
617 self[prefix + "patch"] = patch
618
619 def SetVersion(self, version_file, prefix):
620 output = ""
621 for line in FileToText(version_file).splitlines():
622 if line.startswith("#define MAJOR_VERSION"):
623 line = re.sub("\d+$", self[prefix + "major"], line)
624 elif line.startswith("#define MINOR_VERSION"):
625 line = re.sub("\d+$", self[prefix + "minor"], line)
626 elif line.startswith("#define BUILD_NUMBER"):
627 line = re.sub("\d+$", self[prefix + "build"], line)
628 elif line.startswith("#define PATCH_LEVEL"):
629 line = re.sub("\d+$", self[prefix + "patch"], line)
630 output += "%s\n" % line
631 TextToFile(output, version_file)
632
633
634 class BootstrapStep(Step):
635 MESSAGE = "Bootstapping v8 checkout."
636
637 def RunStep(self):
638 if os.path.realpath(self.default_cwd) == os.path.realpath(V8_BASE):
639 self.Die("Can't use v8 checkout with calling script as work checkout.")
640 # Directory containing the working v8 checkout.
641 if not os.path.exists(self._options.work_dir):
642 os.makedirs(self._options.work_dir)
643 if not os.path.exists(self.default_cwd):
644 self.Command("fetch", "v8", cwd=self._options.work_dir)
645
646
647 class UploadStep(Step):
648 MESSAGE = "Upload for code review."
649
650 def RunStep(self):
651 if self._options.reviewer:
652 print "Using account %s for review." % self._options.reviewer
653 reviewer = self._options.reviewer
654 else:
655 print "Please enter the email address of a V8 reviewer for your patch: ",
656 self.DieNoManualMode("A reviewer must be specified in forced mode.")
657 reviewer = self.ReadLine()
658 self.GitUpload(reviewer, self._options.author, self._options.force_upload,
659 bypass_hooks=self._options.bypass_upload_hooks,
660 cc=self._options.cc)
661
662
663 class DetermineV8Sheriff(Step):
664 MESSAGE = "Determine the V8 sheriff for code review."
665
666 def RunStep(self):
667 self["sheriff"] = None
668 if not self._options.sheriff: # pragma: no cover
669 return
670
671 try:
672 # The googlers mapping maps @google.com accounts to @chromium.org
673 # accounts.
674 googlers = imp.load_source('googlers_mapping',
675 self._options.googlers_mapping)
676 googlers = googlers.list_to_dict(googlers.get_list())
677 except: # pragma: no cover
678 print "Skip determining sheriff without googler mapping."
679 return
680
681 # The sheriff determined by the rotation on the waterfall has a
682 # @google.com account.
683 url = "https://chromium-build.appspot.com/p/chromium/sheriff_v8.js"
684 match = re.match(r"document\.write\('(\w+)'\)", self.ReadURL(url))
685
686 # If "channel is sheriff", we can't match an account.
687 if match:
688 g_name = match.group(1)
689 self["sheriff"] = googlers.get(g_name + "@google.com",
690 g_name + "@chromium.org")
691 self._options.reviewer = self["sheriff"]
692 print "Found active sheriff: %s" % self["sheriff"]
693 else:
694 print "No active sheriff found."
695
696
697 def MakeStep(step_class=Step, number=0, state=None, config=None,
698 options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
699 # Allow to pass in empty dictionaries.
700 state = state if state is not None else {}
701 config = config if config is not None else {}
702
703 try:
704 message = step_class.MESSAGE
705 except AttributeError:
706 message = step_class.__name__
707
708 return step_class(message, number=number, config=config,
709 state=state, options=options,
710 handler=side_effect_handler)
711
712
713 class ScriptsBase(object):
714 def __init__(self,
715 config=None,
716 side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
717 state=None):
718 self._config = config or self._Config()
719 self._side_effect_handler = side_effect_handler
720 self._state = state if state is not None else {}
721
722 def _Description(self):
723 return None
724
725 def _PrepareOptions(self, parser):
726 pass
727
728 def _ProcessOptions(self, options):
729 return True
730
731 def _Steps(self): # pragma: no cover
732 raise Exception("Not implemented.")
733
734 def _Config(self):
735 return {}
736
737 def MakeOptions(self, args=None):
738 parser = argparse.ArgumentParser(description=self._Description())
739 parser.add_argument("-a", "--author", default="",
740 help="The author email used for rietveld.")
741 parser.add_argument("--dry-run", default=False, action="store_true",
742 help="Perform only read-only actions.")
743 parser.add_argument("-g", "--googlers-mapping",
744 help="Path to the script mapping google accounts.")
745 parser.add_argument("-r", "--reviewer", default="",
746 help="The account name to be used for reviews.")
747 parser.add_argument("--sheriff", default=False, action="store_true",
748 help=("Determine current sheriff to review CLs. On "
749 "success, this will overwrite the reviewer "
750 "option."))
751 parser.add_argument("-s", "--step",
752 help="Specify the step where to start work. Default: 0.",
753 default=0, type=int)
754 parser.add_argument("--work-dir",
755 help=("Location where to bootstrap a working v8 "
756 "checkout."))
757 self._PrepareOptions(parser)
758
759 if args is None: # pragma: no cover
760 options = parser.parse_args()
761 else:
762 options = parser.parse_args(args)
763
764 # Process common options.
765 if options.step < 0: # pragma: no cover
766 print "Bad step number %d" % options.step
767 parser.print_help()
768 return None
769 if options.sheriff and not options.googlers_mapping: # pragma: no cover
770 print "To determine the current sheriff, requires the googler mapping"
771 parser.print_help()
772 return None
773
774 # Defaults for options, common to all scripts.
775 options.manual = getattr(options, "manual", True)
776 options.force = getattr(options, "force", False)
777 options.bypass_upload_hooks = False
778
779 # Derived options.
780 options.requires_editor = not options.force
781 options.wait_for_lgtm = not options.force
782 options.force_readline_defaults = not options.manual
783 options.force_upload = not options.manual
784
785 # Process script specific options.
786 if not self._ProcessOptions(options):
787 parser.print_help()
788 return None
789
790 if not options.work_dir:
791 options.work_dir = "/tmp/v8-release-scripts-work-dir"
792 return options
793
794 def RunSteps(self, step_classes, args=None):
795 options = self.MakeOptions(args)
796 if not options:
797 return 1
798
799 state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
800 if options.step == 0 and os.path.exists(state_file):
801 os.remove(state_file)
802
803 steps = []
804 for (number, step_class) in enumerate([BootstrapStep] + step_classes):
805 steps.append(MakeStep(step_class, number, self._state, self._config,
806 options, self._side_effect_handler))
807 for step in steps[options.step:]:
808 if step.Run():
809 return 0
810 return 0
811
812 def Run(self, args=None):
813 return self.RunSteps(self._Steps(), args)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698