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://v8.dev/docs/triage-issues 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=luci.chromium.try:linux-blink-rel 24CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux_optional_gpu_tests_rel 25CQ_INCLUDE_TRYBOTS=luci.chromium.try:mac_optional_gpu_tests_rel 26CQ_INCLUDE_TRYBOTS=luci.chromium.try:win_optional_gpu_tests_rel 27CQ_INCLUDE_TRYBOTS=luci.chromium.try:android_optional_gpu_tests_rel""") 28 29class Preparation(Step): 30 MESSAGE = "Preparation." 31 32 def RunStep(self): 33 self['json_output']['monitoring_state'] = 'preparation' 34 # Update v8 remote tracking branches. 35 self.GitFetchOrigin() 36 self.Git("fetch origin +refs/tags/*:refs/tags/*") 37 38 39class DetectLastRoll(Step): 40 MESSAGE = "Detect commit ID of the last Chromium roll." 41 42 def RunStep(self): 43 self['json_output']['monitoring_state'] = 'detect_last_roll' 44 self["last_roll"] = self._options.last_roll 45 if not self["last_roll"]: 46 # Get last-rolled v8 revision from Chromium's DEPS file. 47 self["last_roll"] = self.Command( 48 "gclient", "getdep -r src/v8", cwd=self._options.chromium).strip() 49 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 LooseVersion(self["last_version"]) < LooseVersion(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("main", 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("R=%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(force=True, 159 bypass_hooks=True, 160 cq=self._options.use_commit_queue, 161 cq_dry_run=self._options.use_dry_run, 162 set_bot_commit=True, 163 cwd=cwd) 164 print("CL uploaded.") 165 else: 166 print("Dry run - don't upload.") 167 168 self.GitCheckout("main", 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