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 sys
8
9from common_includes import *
10
11
12class Preparation(Step):
13  MESSAGE = "Preparation."
14
15  def RunStep(self):
16    # TODO(machenbach): Remove after the git switch.
17    if self.Config("PERSISTFILE_BASENAME") == "/tmp/v8-auto-tag-tempfile":
18      print "This script is disabled until after the v8 git migration."
19      return True
20
21    self.CommonPrepare()
22    self.PrepareBranch()
23    self.GitCheckout("master")
24    self.vc.Pull()
25
26
27class GetTags(Step):
28  MESSAGE = "Get all V8 tags."
29
30  def RunStep(self):
31    self.GitCreateBranch(self._config["BRANCHNAME"])
32    self["tags"] = self.vc.GetTags()
33
34
35class GetOldestUntaggedVersion(Step):
36  MESSAGE = "Check if there's a version on bleeding edge without a tag."
37
38  def RunStep(self):
39    tags = set(self["tags"])
40    self["candidate"] = None
41    self["candidate_version"] = None
42    self["next"] = None
43    self["next_version"] = None
44
45    # Iterate backwards through all automatic version updates.
46    for git_hash in self.GitLog(
47        format="%H", grep="\\[Auto\\-roll\\] Bump up version to").splitlines():
48
49      # Get the version.
50      if not self.GitCheckoutFileSafe(VERSION_FILE, git_hash):
51        continue
52
53      self.ReadAndPersistVersion()
54      version = self.ArrayToVersion("")
55
56      # Strip off trailing patch level (tags don't include tag level 0).
57      if version.endswith(".0"):
58        version = version[:-2]
59
60      # Clean up checked-out version file.
61      self.GitCheckoutFileSafe(VERSION_FILE, "HEAD")
62
63      if version in tags:
64        if self["candidate"]:
65          # Revision "git_hash" is tagged already and "candidate" was the next
66          # newer revision without a tag.
67          break
68        else:
69          print("Stop as %s is the latest version and it has been tagged." %
70                version)
71          self.CommonCleanup()
72          return True
73      else:
74        # This is the second oldest version without a tag.
75        self["next"] = self["candidate"]
76        self["next_version"] = self["candidate_version"]
77
78        # This is the oldest version without a tag.
79        self["candidate"] = git_hash
80        self["candidate_version"] = version
81
82    if not self["candidate"] or not self["candidate_version"]:
83      print "Nothing found to tag."
84      self.CommonCleanup()
85      return True
86
87    print("Candidate for tagging is %s with version %s" %
88          (self["candidate"], self["candidate_version"]))
89
90
91class GetLKGRs(Step):
92  MESSAGE = "Get the last lkgrs."
93
94  def RunStep(self):
95    revision_url = "https://v8-status.appspot.com/revisions?format=json"
96    status_json = self.ReadURL(revision_url, wait_plan=[5, 20])
97    self["lkgrs"] = [entry["revision"]
98                     for entry in json.loads(status_json) if entry["status"]]
99
100
101class CalculateTagRevision(Step):
102  MESSAGE = "Calculate the revision to tag."
103
104  def LastLKGR(self, min_rev, max_rev):
105    """Finds the newest lkgr between min_rev (inclusive) and max_rev
106    (exclusive).
107    """
108    for lkgr in self["lkgrs"]:
109      # LKGRs are reverse sorted.
110      if int(min_rev) <= int(lkgr) and int(lkgr) < int(max_rev):
111        return lkgr
112    return None
113
114  def RunStep(self):
115    # Get the lkgr after the tag candidate and before the next tag candidate.
116    candidate_svn = self.vc.GitSvn(self["candidate"])
117    if self["next"]:
118      next_svn = self.vc.GitSvn(self["next"])
119    else:
120      # Don't include the version change commit itself if there is no upper
121      # limit yet.
122      candidate_svn =  str(int(candidate_svn) + 1)
123      next_svn = sys.maxint
124    lkgr_svn = self.LastLKGR(candidate_svn, next_svn)
125
126    if not lkgr_svn:
127      print "There is no lkgr since the candidate version yet."
128      self.CommonCleanup()
129      return True
130
131    # Let's check if the lkgr is at least three hours old.
132    self["lkgr"] = self.vc.SvnGit(lkgr_svn)
133    if not self["lkgr"]:
134      print "Couldn't find git hash for lkgr %s" % lkgr_svn
135      self.CommonCleanup()
136      return True
137
138    lkgr_utc_time = int(self.GitLog(n=1, format="%at", git_hash=self["lkgr"]))
139    current_utc_time = self._side_effect_handler.GetUTCStamp()
140
141    if current_utc_time < lkgr_utc_time + 10800:
142      print "Candidate lkgr %s is too recent for tagging." % lkgr_svn
143      self.CommonCleanup()
144      return True
145
146    print "Tagging revision %s with %s" % (lkgr_svn, self["candidate_version"])
147
148
149class MakeTag(Step):
150  MESSAGE = "Tag the version."
151
152  def RunStep(self):
153    if not self._options.dry_run:
154      self.GitReset(self["lkgr"])
155      # FIXME(machenbach): Make this work with the git repo.
156      self.vc.Tag(self["candidate_version"],
157                  "svn/bleeding_edge",
158                  "This won't work!")
159
160
161class CleanUp(Step):
162  MESSAGE = "Clean up."
163
164  def RunStep(self):
165    self.CommonCleanup()
166
167
168class AutoTag(ScriptsBase):
169  def _PrepareOptions(self, parser):
170    parser.add_argument("--dry_run", help="Don't tag the new version.",
171                        default=False, action="store_true")
172
173  def _ProcessOptions(self, options):  # pragma: no cover
174    if not options.dry_run and not options.author:
175      print "Specify your chromium.org email with -a"
176      return False
177    options.wait_for_lgtm = False
178    options.force_readline_defaults = True
179    options.force_upload = True
180    return True
181
182  def _Config(self):
183    return {
184      "BRANCHNAME": "auto-tag-v8",
185      "PERSISTFILE_BASENAME": "/tmp/v8-auto-tag-tempfile",
186    }
187
188  def _Steps(self):
189    return [
190      Preparation,
191      GetTags,
192      GetOldestUntaggedVersion,
193      GetLKGRs,
194      CalculateTagRevision,
195      MakeTag,
196      CleanUp,
197    ]
198
199
200if __name__ == "__main__":  # pragma: no cover
201  sys.exit(AutoTag().Run())
202