| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 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 # This script retrieves the history of all V8 branches and trunk revisions and | 
|  | 7 # their corresponding Chromium revisions. | 
|  | 8 | 
|  | 9 import argparse | 
|  | 10 import csv | 
|  | 11 import itertools | 
|  | 12 import json | 
|  | 13 import os | 
|  | 14 import re | 
|  | 15 import sys | 
|  | 16 | 
|  | 17 from common_includes import * | 
|  | 18 | 
|  | 19 DEPS_FILE = "DEPS_FILE" | 
|  | 20 CHROMIUM = "CHROMIUM" | 
|  | 21 | 
|  | 22 CONFIG = { | 
|  | 23   BRANCHNAME: "retrieve-v8-releases", | 
|  | 24   TEMP_BRANCH: "unused-branch", # TODO(machenbach): Remove from infrastructure. | 
|  | 25   PERSISTFILE_BASENAME: "/tmp/v8-releases-tempfile", | 
|  | 26   DOT_GIT_LOCATION: ".git", | 
|  | 27   VERSION_FILE: "src/version.cc", | 
|  | 28   DEPS_FILE: "DEPS", | 
|  | 29 } | 
|  | 30 | 
|  | 31 # Expression for retrieving the bleeding edge revision from a commit message. | 
|  | 32 PUSH_MESSAGE_RE = re.compile(r".* \(based on bleeding_edge revision r(\d+)\)$") | 
|  | 33 | 
|  | 34 # Expression for retrieving the merged patches from a merge commit message | 
|  | 35 # (old and new format). | 
|  | 36 MERGE_MESSAGE_RE = re.compile(r"^.*[M|m]erged (.+)(\)| into).*$", re.M) | 
|  | 37 | 
|  | 38 # Expression for retrieving reverted patches from a commit message (old and | 
|  | 39 # new format). | 
|  | 40 ROLLBACK_MESSAGE_RE = re.compile(r"^.*[R|r]ollback of (.+)(\)| in).*$", re.M) | 
|  | 41 | 
|  | 42 # Expression with three versions (historical) for extracting the v8 revision | 
|  | 43 # from the chromium DEPS file. | 
|  | 44 DEPS_RE = re.compile(r'^\s*(?:"v8_revision": "' | 
|  | 45                       '|\(Var\("googlecode_url"\) % "v8"\) \+ "\/trunk@' | 
|  | 46                       '|"http\:\/\/v8\.googlecode\.com\/svn\/trunk@)' | 
|  | 47                       '([0-9]+)".*$', re.M) | 
|  | 48 | 
|  | 49 | 
|  | 50 def SortingKey(version): | 
|  | 51   """Key for sorting version number strings: '3.11' > '3.2.1.1'""" | 
|  | 52   version_keys = map(int, version.split(".")) | 
|  | 53   # Fill up to full version numbers to normalize comparison. | 
|  | 54   while len(version_keys) < 4: | 
|  | 55     version_keys.append(0) | 
|  | 56   # Fill digits. | 
|  | 57   return ".".join(map("{0:03d}".format, version_keys)) | 
|  | 58 | 
|  | 59 | 
|  | 60 def SortBranches(branches): | 
|  | 61   """Sort branches with version number names.""" | 
|  | 62   return sorted(branches, key=SortingKey, reverse=True) | 
|  | 63 | 
|  | 64 | 
|  | 65 def FilterDuplicatesAndReverse(cr_releases): | 
|  | 66   """Returns the chromium releases in reverse order filtered by v8 revision | 
|  | 67   duplicates. | 
|  | 68 | 
|  | 69   cr_releases is a list of [cr_rev, v8_rev] reverse-sorted by cr_rev. | 
|  | 70   """ | 
|  | 71   last = "" | 
|  | 72   result = [] | 
|  | 73   for release in reversed(cr_releases): | 
|  | 74     if last == release[1]: | 
|  | 75       continue | 
|  | 76     last = release[1] | 
|  | 77     result.append(release) | 
|  | 78   return result | 
|  | 79 | 
|  | 80 | 
|  | 81 def BuildRevisionRanges(cr_releases): | 
|  | 82   """Returns a mapping of v8 revision -> chromium ranges. | 
|  | 83   The ranges are comma-separated, each range has the form R1:R2. The newest | 
|  | 84   entry is the only one of the form R1, as there is no end range. | 
|  | 85 | 
|  | 86   cr_releases is a list of [cr_rev, v8_rev] reverse-sorted by cr_rev. | 
|  | 87   """ | 
|  | 88   range_lists = {} | 
|  | 89   cr_releases = FilterDuplicatesAndReverse(cr_releases) | 
|  | 90 | 
|  | 91   # Visit pairs of cr releases from oldest to newest. | 
|  | 92   for cr_from, cr_to in itertools.izip( | 
|  | 93       cr_releases, itertools.islice(cr_releases, 1, None)): | 
|  | 94 | 
|  | 95     # Assume the chromium revisions are all different. | 
|  | 96     assert cr_from[0] != cr_to[0] | 
|  | 97 | 
|  | 98     # TODO(machenbach): Subtraction is not git friendly. | 
|  | 99     ran = "%s:%d" % (cr_from[0], int(cr_to[0]) - 1) | 
|  | 100 | 
|  | 101     # Collect the ranges in lists per revision. | 
|  | 102     range_lists.setdefault(cr_from[1], []).append(ran) | 
|  | 103 | 
|  | 104   # Add the newest revision. | 
|  | 105   if cr_releases: | 
|  | 106     range_lists.setdefault(cr_releases[-1][1], []).append(cr_releases[-1][0]) | 
|  | 107 | 
|  | 108   # Stringify and comma-separate the range lists. | 
|  | 109   return dict((rev, ",".join(ran)) for rev, ran in range_lists.iteritems()) | 
|  | 110 | 
|  | 111 | 
|  | 112 def MatchSafe(match): | 
|  | 113   if match: | 
|  | 114     return match.group(1) | 
|  | 115   else: | 
|  | 116     return "" | 
|  | 117 | 
|  | 118 | 
|  | 119 class Preparation(Step): | 
|  | 120   MESSAGE = "Preparation." | 
|  | 121 | 
|  | 122   def RunStep(self): | 
|  | 123     self.CommonPrepare() | 
|  | 124     self.PrepareBranch() | 
|  | 125 | 
|  | 126 | 
|  | 127 class RetrieveV8Releases(Step): | 
|  | 128   MESSAGE = "Retrieve all V8 releases." | 
|  | 129 | 
|  | 130   def ExceedsMax(self, releases): | 
|  | 131     return (self._options.max_releases > 0 | 
|  | 132             and len(releases) > self._options.max_releases) | 
|  | 133 | 
|  | 134   def GetBleedingEdgeFromPush(self, title): | 
|  | 135     return MatchSafe(PUSH_MESSAGE_RE.match(title)) | 
|  | 136 | 
|  | 137   def GetMergedPatches(self, git_hash): | 
|  | 138     body = self.GitLog(n=1, format="%B", git_hash=git_hash) | 
|  | 139     patches = MatchSafe(MERGE_MESSAGE_RE.search(body)) | 
|  | 140     if not patches: | 
|  | 141       patches = MatchSafe(ROLLBACK_MESSAGE_RE.search(body)) | 
|  | 142       if patches: | 
|  | 143         # Indicate reverted patches with a "-". | 
|  | 144         patches = "-%s" % patches | 
|  | 145     return patches | 
|  | 146 | 
|  | 147   def GetRelease(self, git_hash, branch): | 
|  | 148     self.ReadAndPersistVersion() | 
|  | 149     base_version = [self["major"], self["minor"], self["build"]] | 
|  | 150     version = ".".join(base_version) | 
|  | 151 | 
|  | 152     patches = "" | 
|  | 153     if self["patch"] != "0": | 
|  | 154       version += ".%s" % self["patch"] | 
|  | 155       patches = self.GetMergedPatches(git_hash) | 
|  | 156 | 
|  | 157     title = self.GitLog(n=1, format="%s", git_hash=git_hash) | 
|  | 158     return { | 
|  | 159       # The SVN revision on the branch. | 
|  | 160       "revision": self.GitSVNFindSVNRev(git_hash), | 
|  | 161       # The SVN revision on bleeding edge (only for newer trunk pushes). | 
|  | 162       "bleeding_edge": self.GetBleedingEdgeFromPush(title), | 
|  | 163       # The branch name. | 
|  | 164       "branch": branch, | 
|  | 165       # The version for displaying in the form 3.26.3 or 3.26.3.12. | 
|  | 166       "version": version, | 
|  | 167       # Merged patches if available in the form 'r1234, r2345'. | 
|  | 168       "patches_merged": patches, | 
|  | 169     }, self["patch"] | 
|  | 170 | 
|  | 171   def GetReleasesFromBranch(self, branch): | 
|  | 172     self.GitReset("svn/%s" % branch) | 
|  | 173     releases = [] | 
|  | 174     try: | 
|  | 175       for git_hash in self.GitLog(format="%H").splitlines(): | 
|  | 176         if self._config[VERSION_FILE] not in self.GitChangedFiles(git_hash): | 
|  | 177           continue | 
|  | 178         if self.ExceedsMax(releases): | 
|  | 179           break  # pragma: no cover | 
|  | 180         if not self.GitCheckoutFileSafe(self._config[VERSION_FILE], git_hash): | 
|  | 181           break  # pragma: no cover | 
|  | 182 | 
|  | 183         release, patch_level = self.GetRelease(git_hash, branch) | 
|  | 184         releases.append(release) | 
|  | 185 | 
|  | 186         # Follow branches only until their creation point. | 
|  | 187         # TODO(machenbach): This omits patches if the version file wasn't | 
|  | 188         # manipulated correctly. Find a better way to detect the point where | 
|  | 189         # the parent of the branch head leads to the trunk branch. | 
|  | 190         if branch != "trunk" and patch_level == "0": | 
|  | 191           break | 
|  | 192 | 
|  | 193     # Allow Ctrl-C interrupt. | 
|  | 194     except (KeyboardInterrupt, SystemExit):  # pragma: no cover | 
|  | 195       pass | 
|  | 196 | 
|  | 197     # Clean up checked-out version file. | 
|  | 198     self.GitCheckoutFileSafe(self._config[VERSION_FILE], "HEAD") | 
|  | 199     return releases | 
|  | 200 | 
|  | 201   def RunStep(self): | 
|  | 202     self.GitCreateBranch(self._config[BRANCHNAME]) | 
|  | 203     # Get relevant remote branches, e.g. "svn/3.25". | 
|  | 204     branches = filter(lambda s: re.match(r"^svn/\d+\.\d+$", s), | 
|  | 205                       self.GitRemotes()) | 
|  | 206     # Remove 'svn/' prefix. | 
|  | 207     branches = map(lambda s: s[4:], branches) | 
|  | 208 | 
|  | 209     releases = [] | 
|  | 210     if self._options.branch == 'recent': | 
|  | 211       # Get only recent development on trunk, beta and stable. | 
|  | 212       if self._options.max_releases == 0:  # pragma: no cover | 
|  | 213         self._options.max_releases = 10 | 
|  | 214       beta, stable = SortBranches(branches)[0:2] | 
|  | 215       releases += self.GetReleasesFromBranch(stable) | 
|  | 216       releases += self.GetReleasesFromBranch(beta) | 
|  | 217       releases += self.GetReleasesFromBranch("trunk") | 
|  | 218     elif self._options.branch == 'all':  # pragma: no cover | 
|  | 219       # Retrieve the full release history. | 
|  | 220       for branch in branches: | 
|  | 221         releases += self.GetReleasesFromBranch(branch) | 
|  | 222       releases += self.GetReleasesFromBranch("trunk") | 
|  | 223     else:  # pragma: no cover | 
|  | 224       # Retrieve history for a specified branch. | 
|  | 225       assert self._options.branch in branches + ["trunk"] | 
|  | 226       releases += self.GetReleasesFromBranch(self._options.branch) | 
|  | 227 | 
|  | 228     self["releases"] = sorted(releases, | 
|  | 229                               key=lambda r: SortingKey(r["version"]), | 
|  | 230                               reverse=True) | 
|  | 231 | 
|  | 232 | 
|  | 233 # TODO(machenbach): Parts of the Chromium setup are c/p from the chromium_roll | 
|  | 234 # script -> unify. | 
|  | 235 class CheckChromium(Step): | 
|  | 236   MESSAGE = "Check the chromium checkout." | 
|  | 237 | 
|  | 238   def Run(self): | 
|  | 239     self["chrome_path"] = self._options.chromium | 
|  | 240 | 
|  | 241 | 
|  | 242 class SwitchChromium(Step): | 
|  | 243   MESSAGE = "Switch to Chromium checkout." | 
|  | 244   REQUIRES = "chrome_path" | 
|  | 245 | 
|  | 246   def RunStep(self): | 
|  | 247     self["v8_path"] = os.getcwd() | 
|  | 248     os.chdir(self["chrome_path"]) | 
|  | 249     # Check for a clean workdir. | 
|  | 250     if not self.GitIsWorkdirClean():  # pragma: no cover | 
|  | 251       self.Die("Workspace is not clean. Please commit or undo your changes.") | 
|  | 252     # Assert that the DEPS file is there. | 
|  | 253     if not os.path.exists(self.Config(DEPS_FILE)):  # pragma: no cover | 
|  | 254       self.Die("DEPS file not present.") | 
|  | 255 | 
|  | 256 | 
|  | 257 class UpdateChromiumCheckout(Step): | 
|  | 258   MESSAGE = "Update the checkout and create a new branch." | 
|  | 259   REQUIRES = "chrome_path" | 
|  | 260 | 
|  | 261   def RunStep(self): | 
|  | 262     os.chdir(self["chrome_path"]) | 
|  | 263     self.GitCheckout("master") | 
|  | 264     self.GitPull() | 
|  | 265     self.GitCreateBranch(self.Config(BRANCHNAME)) | 
|  | 266 | 
|  | 267 | 
|  | 268 class RetrieveChromiumV8Releases(Step): | 
|  | 269   MESSAGE = "Retrieve V8 releases from Chromium DEPS." | 
|  | 270   REQUIRES = "chrome_path" | 
|  | 271 | 
|  | 272   def RunStep(self): | 
|  | 273     os.chdir(self["chrome_path"]) | 
|  | 274 | 
|  | 275     trunk_releases = filter(lambda r: r["branch"] == "trunk", self["releases"]) | 
|  | 276     if not trunk_releases:  # pragma: no cover | 
|  | 277       print "No trunk releases detected. Skipping chromium history." | 
|  | 278       return True | 
|  | 279 | 
|  | 280     oldest_v8_rev = int(trunk_releases[-1]["revision"]) | 
|  | 281 | 
|  | 282     cr_releases = [] | 
|  | 283     try: | 
|  | 284       for git_hash in self.GitLog(format="%H", grep="V8").splitlines(): | 
|  | 285         if self._config[DEPS_FILE] not in self.GitChangedFiles(git_hash): | 
|  | 286           continue | 
|  | 287         if not self.GitCheckoutFileSafe(self._config[DEPS_FILE], git_hash): | 
|  | 288           break  # pragma: no cover | 
|  | 289         deps = FileToText(self.Config(DEPS_FILE)) | 
|  | 290         match = DEPS_RE.search(deps) | 
|  | 291         if match: | 
|  | 292           svn_rev = self.GitSVNFindSVNRev(git_hash) | 
|  | 293           v8_rev = match.group(1) | 
|  | 294           cr_releases.append([svn_rev, v8_rev]) | 
|  | 295 | 
|  | 296           # Stop after reaching beyond the last v8 revision we want to update. | 
|  | 297           # We need a small buffer for possible revert/reland frenzies. | 
|  | 298           # TODO(machenbach): Subtraction is not git friendly. | 
|  | 299           if int(v8_rev) < oldest_v8_rev - 100: | 
|  | 300             break  # pragma: no cover | 
|  | 301 | 
|  | 302     # Allow Ctrl-C interrupt. | 
|  | 303     except (KeyboardInterrupt, SystemExit):  # pragma: no cover | 
|  | 304       pass | 
|  | 305 | 
|  | 306     # Clean up. | 
|  | 307     self.GitCheckoutFileSafe(self._config[DEPS_FILE], "HEAD") | 
|  | 308 | 
|  | 309     # Add the chromium ranges to the v8 trunk releases. | 
|  | 310     all_ranges = BuildRevisionRanges(cr_releases) | 
|  | 311     trunk_dict = dict((r["revision"], r) for r in trunk_releases) | 
|  | 312     for revision, ranges in all_ranges.iteritems(): | 
|  | 313       trunk_dict.get(revision, {})["chromium_revision"] = ranges | 
|  | 314 | 
|  | 315 class SwitchV8(Step): | 
|  | 316   MESSAGE = "Returning to V8 checkout." | 
|  | 317   REQUIRES = "chrome_path" | 
|  | 318 | 
|  | 319   def RunStep(self): | 
|  | 320     self.GitCheckout("master") | 
|  | 321     self.GitDeleteBranch(self.Config(BRANCHNAME)) | 
|  | 322     os.chdir(self["v8_path"]) | 
|  | 323 | 
|  | 324 | 
|  | 325 class CleanUp(Step): | 
|  | 326   MESSAGE = "Clean up." | 
|  | 327 | 
|  | 328   def RunStep(self): | 
|  | 329     self.CommonCleanup() | 
|  | 330 | 
|  | 331 | 
|  | 332 class WriteOutput(Step): | 
|  | 333   MESSAGE = "Print output." | 
|  | 334 | 
|  | 335   def Run(self): | 
|  | 336     if self._options.csv: | 
|  | 337       with open(self._options.csv, "w") as f: | 
|  | 338         writer = csv.DictWriter(f, | 
|  | 339                                 ["version", "branch", "revision", | 
|  | 340                                  "chromium_revision", "patches_merged"], | 
|  | 341                                 restval="", | 
|  | 342                                 extrasaction="ignore") | 
|  | 343         for release in self["releases"]: | 
|  | 344           writer.writerow(release) | 
|  | 345     if self._options.json: | 
|  | 346       with open(self._options.json, "w") as f: | 
|  | 347         f.write(json.dumps(self["releases"])) | 
|  | 348     if not self._options.csv and not self._options.json: | 
|  | 349       print self["releases"]  # pragma: no cover | 
|  | 350 | 
|  | 351 | 
|  | 352 class Releases(ScriptsBase): | 
|  | 353   def _PrepareOptions(self, parser): | 
|  | 354     parser.add_argument("-b", "--branch", default="recent", | 
|  | 355                         help=("The branch to analyze. If 'all' is specified, " | 
|  | 356                               "analyze all branches. If 'recent' (default) " | 
|  | 357                               "is specified, track beta, stable and trunk.")) | 
|  | 358     parser.add_argument("-c", "--chromium", | 
|  | 359                         help=("The path to your Chromium src/ " | 
|  | 360                               "directory to automate the V8 roll.")) | 
|  | 361     parser.add_argument("--csv", help="Path to a CSV file for export.") | 
|  | 362     parser.add_argument("-m", "--max-releases", type=int, default=0, | 
|  | 363                         help="The maximum number of releases to track.") | 
|  | 364     parser.add_argument("--json", help="Path to a JSON file for export.") | 
|  | 365 | 
|  | 366   def _ProcessOptions(self, options):  # pragma: no cover | 
|  | 367     return True | 
|  | 368 | 
|  | 369   def _Steps(self): | 
|  | 370     return [ | 
|  | 371       Preparation, | 
|  | 372       RetrieveV8Releases, | 
|  | 373       CheckChromium, | 
|  | 374       SwitchChromium, | 
|  | 375       UpdateChromiumCheckout, | 
|  | 376       RetrieveChromiumV8Releases, | 
|  | 377       SwitchV8, | 
|  | 378       CleanUp, | 
|  | 379       WriteOutput, | 
|  | 380     ] | 
|  | 381 | 
|  | 382 | 
|  | 383 if __name__ == "__main__":  # pragma: no cover | 
|  | 384   sys.exit(Releases(CONFIG).Run()) | 
| OLD | NEW | 
|---|