• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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