1#!/usr/bin/env python
2# Copyright 2014 the V8 project authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7import os
8import sys
9
10from common_includes import *
11
12ROLL_SUMMARY = ("Summary of changes available at:\n"
13                "https://chromium.googlesource.com/v8/v8/+log/%s..%s")
14
15ISSUE_MSG = (
16"""Please follow these instructions for assigning/CC'ing issues:
17https://github.com/v8/v8/wiki/Triaging%20issues
18
19Please close rolling in case of a roll revert:
20https://v8-roll.appspot.com/
21This only works with a Google account.
22
23CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel""")
24
25class Preparation(Step):
26  MESSAGE = "Preparation."
27
28  def RunStep(self):
29    self['json_output']['monitoring_state'] = 'preparation'
30    # Update v8 remote tracking branches.
31    self.GitFetchOrigin()
32    self.Git("fetch origin +refs/tags/*:refs/tags/*")
33
34
35class DetectLastRoll(Step):
36  MESSAGE = "Detect commit ID of the last Chromium roll."
37
38  def RunStep(self):
39    self['json_output']['monitoring_state'] = 'detect_last_roll'
40    self["last_roll"] = self._options.last_roll
41    if not self["last_roll"]:
42      # Interpret the DEPS file to retrieve the v8 revision.
43      # TODO(machenbach): This should be part or the roll-deps api of
44      # depot_tools.
45      Var = lambda var: '%s'
46      exec(FileToText(os.path.join(self._options.chromium, "DEPS")))
47
48      # The revision rolled last.
49      self["last_roll"] = vars['v8_revision']
50    self["last_version"] = self.GetVersionTag(self["last_roll"])
51    assert self["last_version"], "The last rolled v8 revision is not tagged."
52
53
54class DetectRevisionToRoll(Step):
55  MESSAGE = "Detect commit ID of the V8 revision to roll."
56
57  def RunStep(self):
58    self['json_output']['monitoring_state'] = 'detect_revision'
59    self["roll"] = self._options.revision
60    if self["roll"]:
61      # If the revision was passed on the cmd line, continue script execution
62      # in the next step.
63      return False
64
65    # The revision that should be rolled. Check for the latest of the most
66    # recent releases based on commit timestamp.
67    revisions = self.GetRecentReleases(
68        max_age=self._options.max_age * DAY_IN_SECONDS)
69    assert revisions, "Didn't find any recent release."
70
71    # There must be some progress between the last roll and the new candidate
72    # revision (i.e. we don't go backwards). The revisions are ordered newest
73    # to oldest. It is possible that the newest timestamp has no progress
74    # compared to the last roll, i.e. if the newest release is a cherry-pick
75    # on a release branch. Then we look further.
76    for revision in revisions:
77      version = self.GetVersionTag(revision)
78      assert version, "Internal error. All recent releases should have a tag"
79
80      if SortingKey(self["last_version"]) < SortingKey(version):
81        self["roll"] = revision
82        break
83    else:
84      print("There is no newer v8 revision than the one in Chromium (%s)."
85            % self["last_roll"])
86      self['json_output']['monitoring_state'] = 'up_to_date'
87      return True
88
89
90class PrepareRollCandidate(Step):
91  MESSAGE = "Robustness checks of the roll candidate."
92
93  def RunStep(self):
94    self['json_output']['monitoring_state'] = 'prepare_candidate'
95    self["roll_title"] = self.GitLog(n=1, format="%s",
96                                     git_hash=self["roll"])
97
98    # Make sure the last roll and the roll candidate are releases.
99    version = self.GetVersionTag(self["roll"])
100    assert version, "The revision to roll is not tagged."
101    version = self.GetVersionTag(self["last_roll"])
102    assert version, "The revision used as last roll is not tagged."
103
104
105class SwitchChromium(Step):
106  MESSAGE = "Switch to Chromium checkout."
107
108  def RunStep(self):
109    self['json_output']['monitoring_state'] = 'switch_chromium'
110    cwd = self._options.chromium
111    self.InitialEnvironmentChecks(cwd)
112    # Check for a clean workdir.
113    if not self.GitIsWorkdirClean(cwd=cwd):  # pragma: no cover
114      self.Die("Workspace is not clean. Please commit or undo your changes.")
115    # Assert that the DEPS file is there.
116    if not os.path.exists(os.path.join(cwd, "DEPS")):  # pragma: no cover
117      self.Die("DEPS file not present.")
118
119
120class UpdateChromiumCheckout(Step):
121  MESSAGE = "Update the checkout and create a new branch."
122
123  def RunStep(self):
124    self['json_output']['monitoring_state'] = 'update_chromium'
125    cwd = self._options.chromium
126    self.GitCheckout("master", cwd=cwd)
127    self.DeleteBranch("work-branch", cwd=cwd)
128    self.GitPull(cwd=cwd)
129
130    # Update v8 remotes.
131    self.GitFetchOrigin()
132
133    self.GitCreateBranch("work-branch", cwd=cwd)
134
135
136class UploadCL(Step):
137  MESSAGE = "Create and upload CL."
138
139  def RunStep(self):
140    self['json_output']['monitoring_state'] = 'upload'
141    cwd = self._options.chromium
142    # Patch DEPS file.
143    if self.Command("roll-dep-svn", "v8 %s" %
144                    self["roll"], cwd=cwd) is None:
145      self.Die("Failed to create deps for %s" % self["roll"])
146
147    message = []
148    message.append("Update V8 to %s." % self["roll_title"].lower())
149
150    message.append(
151        ROLL_SUMMARY % (self["last_roll"][:8], self["roll"][:8]))
152
153    message.append(ISSUE_MSG)
154
155    message.append("TBR=%s" % self._options.reviewer)
156    self.GitCommit("\n\n".join(message),  author=self._options.author, cwd=cwd)
157    if not self._options.dry_run:
158      self.GitUpload(author=self._options.author,
159                     force=True,
160                     bypass_hooks=True,
161                     cq=self._options.use_commit_queue,
162                     cwd=cwd)
163      print "CL uploaded."
164    else:
165      print "Dry run - don't upload."
166
167    self.GitCheckout("master", cwd=cwd)
168    self.GitDeleteBranch("work-branch", cwd=cwd)
169
170class CleanUp(Step):
171  MESSAGE = "Done!"
172
173  def RunStep(self):
174    self['json_output']['monitoring_state'] = 'success'
175    print("Congratulations, you have successfully rolled %s into "
176          "Chromium."
177          % self["roll"])
178
179    # Clean up all temporary files.
180    Command("rm", "-f %s*" % self._config["PERSISTFILE_BASENAME"])
181
182
183class AutoRoll(ScriptsBase):
184  def _PrepareOptions(self, parser):
185    parser.add_argument("-c", "--chromium", required=True,
186                        help=("The path to your Chromium src/ "
187                              "directory to automate the V8 roll."))
188    parser.add_argument("--last-roll",
189                        help="The git commit ID of the last rolled version. "
190                             "Auto-detected if not specified.")
191    parser.add_argument("--max-age", default=7, type=int,
192                        help="Maximum age in days of the latest release.")
193    parser.add_argument("--revision",
194                        help="Revision to roll. Auto-detected if not "
195                             "specified."),
196    parser.add_argument("--roll", help="Deprecated.",
197                        default=True, action="store_true")
198    parser.add_argument("--use-commit-queue",
199                        help="Check the CQ bit on upload.",
200                        default=True, action="store_true")
201
202  def _ProcessOptions(self, options):  # pragma: no cover
203    if not options.author or not options.reviewer:
204      print "A reviewer (-r) and an author (-a) are required."
205      return False
206
207    options.requires_editor = False
208    options.force = True
209    options.manual = False
210    return True
211
212  def _Config(self):
213    return {
214      "PERSISTFILE_BASENAME": "/tmp/v8-chromium-roll-tempfile",
215    }
216
217  def _Steps(self):
218    return [
219      Preparation,
220      DetectLastRoll,
221      DetectRevisionToRoll,
222      PrepareRollCandidate,
223      SwitchChromium,
224      UpdateChromiumCheckout,
225      UploadCL,
226      CleanUp,
227    ]
228
229
230if __name__ == "__main__":  # pragma: no cover
231  sys.exit(AutoRoll().Run())
232