1#!/usr/bin/env python3 2# Copyright 2015 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 9import tempfile 10 11from common_includes import * 12 13import urllib.request as urllib2 14 15 16class Preparation(Step): 17 MESSAGE = "Preparation." 18 19 def RunStep(self): 20 self.Git("fetch origin +refs/heads/*:refs/heads/*") 21 self.GitCheckout("origin/main") 22 self.DeleteBranch("work-branch") 23 24 25class PrepareBranchRevision(Step): 26 MESSAGE = "Check from which revision to branch off." 27 28 def RunStep(self): 29 self["push_hash"] = (self._options.revision or 30 self.GitLog(n=1, format="%H", branch="origin/main")) 31 assert self["push_hash"] 32 print("Release revision %s" % self["push_hash"]) 33 34 35class IncrementVersion(Step): 36 MESSAGE = "Increment version number." 37 38 def RunStep(self): 39 latest_version = self.GetLatestVersion() 40 41 # The version file on main can be used to bump up major/minor at 42 # branch time. 43 self.GitCheckoutFile(VERSION_FILE, self.vc.RemoteMainBranch()) 44 self.ReadAndPersistVersion("main_") 45 main_version = self.ArrayToVersion("main_") 46 47 # Use the highest version from main or from tags to determine the new 48 # version. 49 authoritative_version = sorted( 50 [main_version, latest_version], key=LooseVersion)[1] 51 self.StoreVersion(authoritative_version, "authoritative_") 52 53 # Variables prefixed with 'new_' contain the new version numbers for the 54 # ongoing candidates push. 55 self["new_major"] = self["authoritative_major"] 56 self["new_minor"] = self["authoritative_minor"] 57 self["new_build"] = str(int(self["authoritative_build"]) + 1) 58 59 # Make sure patch level is 0 in a new push. 60 self["new_patch"] = "0" 61 62 # The new version is not a candidate. 63 self["new_candidate"] = "0" 64 65 self["version"] = "%s.%s.%s" % (self["new_major"], 66 self["new_minor"], 67 self["new_build"]) 68 69 print ("Incremented version to %s" % self["version"]) 70 71 72class DetectLastRelease(Step): 73 MESSAGE = "Detect commit ID of last release base." 74 75 def RunStep(self): 76 self["last_push_main"] = self.GetLatestReleaseBase() 77 78 79class DeleteBranchRef(Step): 80 MESSAGE = "Delete branch ref." 81 82 def RunStep(self): 83 cmd = "push origin :refs/heads/%s" % self["version"] 84 if self._options.dry_run: 85 print("Dry run. Command:\ngit %s" % cmd) 86 else: 87 try: 88 self.Git(cmd) 89 except Exception: 90 # Be forgiving if branch ref does not exist. 91 pass 92 93 94class PushBranchRef(Step): 95 MESSAGE = "Create branch ref." 96 97 def RunStep(self): 98 cmd = "push origin %s:refs/heads/%s" % (self["push_hash"], self["version"]) 99 if self._options.dry_run: 100 print("Dry run. Command:\ngit %s" % cmd) 101 else: 102 self.Git(cmd) 103 104 105class MakeBranch(Step): 106 MESSAGE = "Create the branch." 107 108 def RunStep(self): 109 self.Git("reset --hard origin/main") 110 self.Git("new-branch work-branch --upstream origin/%s" % self["version"]) 111 self.GitCheckoutFile(VERSION_FILE, self["latest_version"]) 112 113 114class SetVersion(Step): 115 MESSAGE = "Set correct version for candidates." 116 117 def RunStep(self): 118 self.SetVersion(os.path.join(self.default_cwd, VERSION_FILE), "new_") 119 120 121class EnableMergeWatchlist(Step): 122 MESSAGE = "Enable watchlist entry for merge notifications." 123 124 def RunStep(self): 125 old_watchlist_content = FileToText(os.path.join(self.default_cwd, 126 WATCHLISTS_FILE)) 127 new_watchlist_content = re.sub("(# 'v8-merges@googlegroups\.com',)", 128 "'v8-merges@googlegroups.com',", 129 old_watchlist_content) 130 TextToFile(new_watchlist_content, os.path.join(self.default_cwd, 131 WATCHLISTS_FILE)) 132 133 134class CommitBranch(Step): 135 MESSAGE = "Commit version to new branch." 136 137 def RunStep(self): 138 self["commit_title"] = "Version %s" % self["version"] 139 text = "%s" % (self["commit_title"]) 140 TextToFile(text, self.Config("COMMITMSG_FILE")) 141 142 self.GitCommit(file_name=self.Config("COMMITMSG_FILE")) 143 144 145class LandBranch(Step): 146 MESSAGE = "Upload and land changes." 147 148 def RunStep(self): 149 if self._options.dry_run: 150 print("Dry run - upload CL.") 151 else: 152 self.GitUpload(force=True, 153 bypass_hooks=True, 154 no_autocc=True, 155 set_bot_commit=True, 156 message_file=self.Config("COMMITMSG_FILE")) 157 # TODO(crbug.com/1176141): This might need to go through CQ. 158 # We'd need to wait for it to land and then tag it. 159 cmd = "cl land --bypass-hooks -f" 160 if self._options.dry_run: 161 print("Dry run. Command:\ngit %s" % cmd) 162 else: 163 self.Git(cmd) 164 165 os.remove(self.Config("COMMITMSG_FILE")) 166 167 168class TagRevision(Step): 169 MESSAGE = "Tag the new revision." 170 171 def RunStep(self): 172 if self._options.dry_run: 173 print ("Dry run. Tagging \"%s\" with %s" % 174 (self["commit_title"], self["version"])) 175 else: 176 self.vc.Tag(self["version"], 177 "origin/%s" % self["version"], 178 self["commit_title"]) 179 180 181class CleanUp(Step): 182 MESSAGE = "Done!" 183 184 def RunStep(self): 185 print("Congratulations, you have successfully created version %s." 186 % self["version"]) 187 188 self.GitCheckout("origin/main") 189 self.DeleteBranch("work-branch") 190 self.Git("gc") 191 192 193class CreateRelease(ScriptsBase): 194 def _PrepareOptions(self, parser): 195 group = parser.add_mutually_exclusive_group() 196 group.add_argument("-f", "--force", 197 help="Don't prompt the user.", 198 default=True, action="store_true") 199 group.add_argument("-m", "--manual", 200 help="Prompt the user at every important step.", 201 default=False, action="store_true") 202 parser.add_argument("-R", "--revision", 203 help="The git commit ID to push (defaults to HEAD).") 204 205 def _ProcessOptions(self, options): # pragma: no cover 206 if not options.author or not options.reviewer: 207 print("Reviewer (-r) and author (-a) are required.") 208 return False 209 return True 210 211 def _Config(self): 212 return { 213 "PERSISTFILE_BASENAME": "/tmp/create-releases-tempfile", 214 "COMMITMSG_FILE": "/tmp/v8-create-releases-tempfile-commitmsg", 215 } 216 217 def _Steps(self): 218 return [ 219 Preparation, 220 PrepareBranchRevision, 221 IncrementVersion, 222 DetectLastRelease, 223 DeleteBranchRef, 224 PushBranchRef, 225 MakeBranch, 226 SetVersion, 227 EnableMergeWatchlist, 228 CommitBranch, 229 LandBranch, 230 TagRevision, 231 CleanUp, 232 ] 233 234 235if __name__ == "__main__": # pragma: no cover 236 sys.exit(CreateRelease().Run()) 237