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
6"""
7Script for auto-increasing the version on bleeding_edge.
8
9The script can be run regularly by a cron job. It will increase the build
10level of the version on bleeding_edge if:
11- the lkgr version is smaller than the version of the latest revision,
12- the lkgr version is not a version change itself,
13- the tree is not closed for maintenance.
14
15The new version will be the maximum of the bleeding_edge and trunk versions +1.
16E.g. latest bleeding_edge version: 3.22.11.0 and latest trunk 3.23.0.0 gives
17the new version 3.23.1.0.
18
19This script requires a depot tools git checkout. I.e. 'fetch v8'.
20"""
21
22import argparse
23import os
24import sys
25
26from common_includes import *
27
28VERSION_BRANCH = "auto-bump-up-version"
29
30
31class Preparation(Step):
32  MESSAGE = "Preparation."
33
34  def RunStep(self):
35    # Check for a clean workdir.
36    if not self.GitIsWorkdirClean():  # pragma: no cover
37      # This is in case a developer runs this script on a dirty tree.
38      self.GitStash()
39
40    # TODO(machenbach): This should be called master after the git switch.
41    self.GitCheckout("bleeding_edge")
42
43    self.GitPull()
44
45    # Ensure a clean version branch.
46    self.DeleteBranch(VERSION_BRANCH)
47
48
49class GetCurrentBleedingEdgeVersion(Step):
50  MESSAGE = "Get latest bleeding edge version."
51
52  def RunStep(self):
53    # TODO(machenbach): This should be called master after the git switch.
54    self.GitCheckout("bleeding_edge")
55
56    # Store latest version and revision.
57    self.ReadAndPersistVersion()
58    self["latest_version"] = self.ArrayToVersion("")
59    self["latest"] = self.GitLog(n=1, format="%H")
60    print "Bleeding edge version: %s" % self["latest_version"]
61
62
63# This step is pure paranoia. It forbids the script to continue if the last
64# commit changed version.cc. Just in case the other bailout has a bug, this
65# prevents the script from continuously commiting version changes.
66class LastChangeBailout(Step):
67  MESSAGE = "Stop script if the last change modified the version."
68
69  def RunStep(self):
70    if VERSION_FILE in self.GitChangedFiles(self["latest"]):
71      print "Stop due to recent version change."
72      return True
73
74
75# TODO(machenbach): Implement this for git.
76class FetchLKGR(Step):
77  MESSAGE = "Fetching V8 LKGR."
78
79  def RunStep(self):
80    lkgr_url = "https://v8-status.appspot.com/lkgr"
81    self["lkgr_svn"] = self.ReadURL(lkgr_url, wait_plan=[5])
82
83
84# TODO(machenbach): Implement this for git. With a git lkgr we could simply
85# checkout that revision. With svn, we have to search backwards until that
86# revision is found.
87class GetLKGRVersion(Step):
88  MESSAGE = "Get bleeding edge lkgr version."
89
90  def RunStep(self):
91    self.GitCheckout("bleeding_edge")
92    # If the commit was made from svn, there is a mapping entry in the commit
93    # message.
94    self["lkgr"] = self.GitLog(
95        grep="^git-svn-id: [^@]*@%s [A-Za-z0-9-]*$" % self["lkgr_svn"],
96        format="%H")
97
98    # FIXME(machenbach): http://crbug.com/391712 can lead to svn lkgrs on the
99    # trunk branch (rarely).
100    if not self["lkgr"]:  # pragma: no cover
101      self.Die("No git hash found for svn lkgr.")
102
103    self.GitCreateBranch(VERSION_BRANCH, self["lkgr"])
104    self.ReadAndPersistVersion("lkgr_")
105    self["lkgr_version"] = self.ArrayToVersion("lkgr_")
106    print "LKGR version: %s" % self["lkgr_version"]
107
108    # Ensure a clean version branch.
109    self.GitCheckout("bleeding_edge")
110    self.DeleteBranch(VERSION_BRANCH)
111
112
113class LKGRVersionUpToDateBailout(Step):
114  MESSAGE = "Stop script if the lkgr has a renewed version."
115
116  def RunStep(self):
117    # If a version-change commit becomes the lkgr, don't bump up the version
118    # again.
119    if VERSION_FILE in self.GitChangedFiles(self["lkgr"]):
120      print "Stop because the lkgr is a version change itself."
121      return True
122
123    # Don't bump up the version if it got updated already after the lkgr.
124    if SortingKey(self["lkgr_version"]) < SortingKey(self["latest_version"]):
125      print("Stop because the latest version already changed since the lkgr "
126            "version.")
127      return True
128
129
130class GetTrunkVersion(Step):
131  MESSAGE = "Get latest trunk version."
132
133  def RunStep(self):
134    # TODO(machenbach): This should be called trunk after the git switch.
135    self.GitCheckout("master")
136    self.GitPull()
137    self.ReadAndPersistVersion("trunk_")
138    self["trunk_version"] = self.ArrayToVersion("trunk_")
139    print "Trunk version: %s" % self["trunk_version"]
140
141
142class CalculateVersion(Step):
143  MESSAGE = "Calculate the new version."
144
145  def RunStep(self):
146    if self["lkgr_build"] == "9999":  # pragma: no cover
147      # If version control on bleeding edge was switched off, just use the last
148      # trunk version.
149      self["lkgr_version"] = self["trunk_version"]
150
151    # The new version needs to be greater than the max on bleeding edge and
152    # trunk.
153    max_version = max(self["trunk_version"],
154                      self["lkgr_version"],
155                      key=SortingKey)
156
157    # Strip off possible leading zeros.
158    self["new_major"], self["new_minor"], self["new_build"], _ = (
159        map(str, map(int, max_version.split("."))))
160
161    self["new_build"] = str(int(self["new_build"]) + 1)
162    self["new_patch"] = "0"
163
164    self["new_version"] = ("%s.%s.%s.0" %
165        (self["new_major"], self["new_minor"], self["new_build"]))
166    print "New version is %s" % self["new_version"]
167
168    if self._options.dry_run:  # pragma: no cover
169      print "Dry run, skipping version change."
170      return True
171
172
173class CheckTreeStatus(Step):
174  MESSAGE = "Checking v8 tree status message."
175
176  def RunStep(self):
177    status_url = "https://v8-status.appspot.com/current?format=json"
178    status_json = self.ReadURL(status_url, wait_plan=[5, 20, 300, 300])
179    message = json.loads(status_json)["message"]
180    if re.search(r"maintenance|no commits", message, flags=re.I):
181      print "Skip version change by tree status: \"%s\"" % message
182      return True
183
184
185class ChangeVersion(Step):
186  MESSAGE = "Bump up the version."
187
188  def RunStep(self):
189    self.GitCreateBranch(VERSION_BRANCH, "bleeding_edge")
190
191    self.SetVersion(os.path.join(self.default_cwd, VERSION_FILE), "new_")
192
193    try:
194      msg = "[Auto-roll] Bump up version to %s" % self["new_version"]
195      self.GitCommit("%s\n\nTBR=%s" % (msg, self._options.author),
196                     author=self._options.author)
197      if self._options.svn:
198        self.SVNCommit("branches/bleeding_edge", msg)
199      else:
200        self.GitUpload(author=self._options.author,
201                       force=self._options.force_upload,
202                       bypass_hooks=True)
203        self.GitDCommit()
204      print "Successfully changed the version."
205    finally:
206      # Clean up.
207      self.GitCheckout("bleeding_edge")
208      self.DeleteBranch(VERSION_BRANCH)
209
210
211class BumpUpVersion(ScriptsBase):
212  def _PrepareOptions(self, parser):
213    parser.add_argument("--dry_run", help="Don't commit the new version.",
214                        default=False, action="store_true")
215
216  def _ProcessOptions(self, options):  # pragma: no cover
217    if not options.dry_run and not options.author:
218      print "Specify your chromium.org email with -a"
219      return False
220    options.wait_for_lgtm = False
221    options.force_readline_defaults = True
222    options.force_upload = True
223    return True
224
225  def _Config(self):
226    return {
227      "PERSISTFILE_BASENAME": "/tmp/v8-bump-up-version-tempfile",
228    }
229
230  def _Steps(self):
231    return [
232      Preparation,
233      GetCurrentBleedingEdgeVersion,
234      LastChangeBailout,
235      FetchLKGR,
236      GetLKGRVersion,
237      LKGRVersionUpToDateBailout,
238      GetTrunkVersion,
239      CalculateVersion,
240      CheckTreeStatus,
241      ChangeVersion,
242    ]
243
244if __name__ == "__main__":  # pragma: no cover
245  sys.exit(BumpUpVersion().Run())
246