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;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel;luci.chromium.try:android_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 setdep 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("gclient", "setdep -r src/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                     cq_dry_run=self._options.use_dry_run,
163                     cwd=cwd)
164      print "CL uploaded."
165    else:
166      print "Dry run - don't upload."
167
168    self.GitCheckout("master", cwd=cwd)
169    self.GitDeleteBranch("work-branch", cwd=cwd)
170
171class CleanUp(Step):
172  MESSAGE = "Done!"
173
174  def RunStep(self):
175    self['json_output']['monitoring_state'] = 'success'
176    print("Congratulations, you have successfully rolled %s into "
177          "Chromium."
178          % self["roll"])
179
180    # Clean up all temporary files.
181    Command("rm", "-f %s*" % self._config["PERSISTFILE_BASENAME"])
182
183
184class AutoRoll(ScriptsBase):
185  def _PrepareOptions(self, parser):
186    parser.add_argument("-c", "--chromium", required=True,
187                        help=("The path to your Chromium src/ "
188                              "directory to automate the V8 roll."))
189    parser.add_argument("--last-roll",
190                        help="The git commit ID of the last rolled version. "
191                             "Auto-detected if not specified.")
192    parser.add_argument("--max-age", default=7, type=int,
193                        help="Maximum age in days of the latest release.")
194    parser.add_argument("--revision",
195                        help="Revision to roll. Auto-detected if not "
196                             "specified."),
197    parser.add_argument("--roll", help="Deprecated.",
198                        default=True, action="store_true")
199    group = parser.add_mutually_exclusive_group()
200    group.add_argument("--use-commit-queue",
201                       help="Trigger the CQ full run on upload.",
202                       default=False, action="store_true")
203    group.add_argument("--use-dry-run",
204                       help="Trigger the CQ dry run on upload.",
205                       default=True, action="store_true")
206
207  def _ProcessOptions(self, options):  # pragma: no cover
208    if not options.author or not options.reviewer:
209      print "A reviewer (-r) and an author (-a) are required."
210      return False
211
212    options.requires_editor = False
213    options.force = True
214    options.manual = False
215    return True
216
217  def _Config(self):
218    return {
219      "PERSISTFILE_BASENAME": "/tmp/v8-chromium-roll-tempfile",
220    }
221
222  def _Steps(self):
223    return [
224      Preparation,
225      DetectLastRoll,
226      DetectRevisionToRoll,
227      PrepareRollCandidate,
228      SwitchChromium,
229      UpdateChromiumCheckout,
230      UploadCL,
231      CleanUp,
232    ]
233
234
235if __name__ == "__main__":  # pragma: no cover
236  sys.exit(AutoRoll().Run())
237