Add script to bump up version on bleeding edge.

The version.cc file will from now on be controlled on
bleeding edge and on trunk. The next version to use will
always be max(bleeding_edge_version, trunk_version) + 1.

The trunk and bleeding_edge versions are not semantically
tied together. The trunk branch can be used to make
independent point releases of bleeding_edge revisions.

The version on bleeding_edge will be increased if:
- the lkgr version is smaller than the version of the latest
  revision,
- the lkgr version is not a version change itself,
- the tree is not closed for maintenance.

This will allow chromium to refer directly to bleeding_edge
revisions of v8. The v8 versions will not be unique, but
they will change as often as possible, dependent on the lkgr
cycle time and the frequency this script is called.

BUG=391261
LOG=n
TEST=tools/push-to-trunk/script_test.py
TEST=tools/push-to-trunk/bump_up_version.py --dry_run
R=jarin@chromium.org

Review URL: https://codereview.chromium.org/389353003

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22421 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
machenbach@chromium.org 2014-07-16 07:57:13 +00:00
parent 49ae3081d2
commit 570ea3bf5d
6 changed files with 406 additions and 28 deletions

View File

@ -0,0 +1,240 @@
#!/usr/bin/env python
# Copyright 2014 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Script for auto-increasing the version on bleeding_edge.
The script can be run regularly by a cron job. It will increase the build
level of the version on bleeding_edge if:
- the lkgr version is smaller than the version of the latest revision,
- the lkgr version is not a version change itself,
- the tree is not closed for maintenance.
The new version will be the maximum of the bleeding_edge and trunk versions +1.
E.g. latest bleeding_edge version: 3.22.11.0 and latest trunk 3.23.0.0 gives
the new version 3.23.1.0.
This script requires a depot tools git checkout. I.e. 'fetch v8'.
"""
import argparse
import os
import sys
from common_includes import *
CONFIG = {
PERSISTFILE_BASENAME: "/tmp/v8-bump-up-version-tempfile",
VERSION_FILE: "src/version.cc",
}
VERSION_BRANCH = "auto-bump-up-version"
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
# Check for a clean workdir.
if not self.GitIsWorkdirClean(): # pragma: no cover
# This is in case a developer runs this script on a dirty tree.
self.GitStash()
# TODO(machenbach): This should be called master after the git switch.
self.GitCheckout("bleeding_edge")
self.GitPull()
# Ensure a clean version branch.
self.DeleteBranch(VERSION_BRANCH)
class GetCurrentBleedingEdgeVersion(Step):
MESSAGE = "Get latest bleeding edge version."
def RunStep(self):
# TODO(machenbach): This should be called master after the git switch.
self.GitCheckout("bleeding_edge")
# Store latest version and revision.
self.ReadAndPersistVersion()
self["latest_version"] = self.ArrayToVersion("")
self["latest"] = self.GitLog(n=1, format="%H")
print "Bleeding edge version: %s" % self["latest_version"]
# This step is pure paranoia. It forbids the script to continue if the last
# commit changed version.cc. Just in case the other bailout has a bug, this
# prevents the script from continuously commiting version changes.
class LastChangeBailout(Step):
MESSAGE = "Stop script if the last change modified the version."
def RunStep(self):
if self._config[VERSION_FILE] in self.GitChangedFiles(self["latest"]):
print "Stop due to recent version change."
return True
# TODO(machenbach): Implement this for git.
class FetchLKGR(Step):
MESSAGE = "Fetching V8 LKGR."
def RunStep(self):
lkgr_url = "https://v8-status.appspot.com/lkgr"
self["lkgr_svn"] = self.ReadURL(lkgr_url, wait_plan=[5])
# TODO(machenbach): Implement this for git. With a git lkgr we could simply
# checkout that revision. With svn, we have to search backwards until that
# revision is found.
class GetLKGRVersion(Step):
MESSAGE = "Get bleeding edge lkgr version."
def RunStep(self):
self.GitCheckout("bleeding_edge")
# If the commit was made from svn, there is a mapping entry in the commit
# message.
self["lkgr"] = self.GitLog(
grep="^git-svn-id: [^@]*@%s [A-Za-z0-9-]*$" % self["lkgr_svn"],
format="%H")
# FIXME(machenbach): http://crbug.com/391712 can lead to svn lkgrs on the
# trunk branch (rarely).
if not self["lkgr"]: # pragma: no cover
self.Die("No git hash found for svn lkgr.")
self.GitCreateBranch(VERSION_BRANCH, self["lkgr"])
self.ReadAndPersistVersion("lkgr_")
self["lkgr_version"] = self.ArrayToVersion("lkgr_")
print "LKGR version: %s" % self["lkgr_version"]
# Ensure a clean version branch.
self.GitCheckout("bleeding_edge")
self.DeleteBranch(VERSION_BRANCH)
class LKGRVersionUpToDateBailout(Step):
MESSAGE = "Stop script if the lkgr has a renewed version."
def RunStep(self):
# If a version-change commit becomes the lkgr, don't bump up the version
# again.
if self._config[VERSION_FILE] in self.GitChangedFiles(self["lkgr"]):
print "Stop because the lkgr is a version change itself."
return True
# Don't bump up the version if it got updated already after the lkgr.
if SortingKey(self["lkgr_version"]) < SortingKey(self["latest_version"]):
print("Stop because the latest version already changed since the lkgr "
"version.")
return True
class GetTrunkVersion(Step):
MESSAGE = "Get latest trunk version."
def RunStep(self):
# TODO(machenbach): This should be called trunk after the git switch.
self.GitCheckout("master")
self.GitPull()
self.ReadAndPersistVersion("trunk_")
self["trunk_version"] = self.ArrayToVersion("trunk_")
print "Trunk version: %s" % self["trunk_version"]
class CalculateVersion(Step):
MESSAGE = "Calculate the new version."
def RunStep(self):
if self["lkgr_build"] == "9999": # pragma: no cover
# If version control on bleeding edge was switched off, just use the last
# trunk version.
self["lkgr_version"] = self["trunk_version"]
# The new version needs to be greater than the max on bleeding edge and
# trunk.
max_version = max(self["trunk_version"],
self["lkgr_version"],
key=SortingKey)
# Strip off possible leading zeros.
self["new_major"], self["new_minor"], self["new_build"], _ = (
map(str, map(int, max_version.split("."))))
self["new_build"] = str(int(self["new_build"]) + 1)
self["new_patch"] = "0"
self["new_version"] = ("%s.%s.%s.0" %
(self["new_major"], self["new_minor"], self["new_build"]))
print "New version is %s" % self["new_version"]
if self._options.dry_run: # pragma: no cover
print "Dry run, skipping version change."
return True
class CheckTreeStatus(Step):
MESSAGE = "Checking v8 tree status message."
def RunStep(self):
status_url = "https://v8-status.appspot.com/current?format=json"
status_json = self.ReadURL(status_url, wait_plan=[5, 20, 300, 300])
message = json.loads(status_json)["message"]
if re.search(r"maintenance|no commits", message, flags=re.I):
print "Skip version change by tree status: \"%s\"" % message
return True
class ChangeVersion(Step):
MESSAGE = "Bump up the version."
def RunStep(self):
self.GitCreateBranch(VERSION_BRANCH, "bleeding_edge")
self.SetVersion(self.Config(VERSION_FILE), "new_")
try:
self.GitCommit("[Auto-roll] Bump up version to %s\n\nTBR=%s" %
(self["new_version"], self._options.author))
self.GitUpload(author=self._options.author,
force=self._options.force_upload)
self.GitDCommit()
print "Successfully changed the version."
finally:
# Clean up.
self.GitCheckout("bleeding_edge")
self.DeleteBranch(VERSION_BRANCH)
class BumpUpVersion(ScriptsBase):
def _PrepareOptions(self, parser):
parser.add_argument("--dry_run", help="Don't commit the new version.",
default=False, action="store_true")
def _ProcessOptions(self, options): # pragma: no cover
if not options.dry_run and not options.author:
print "Specify your chromium.org email with -a"
return False
options.wait_for_lgtm = False
options.force_readline_defaults = True
options.force_upload = True
return True
def _Steps(self):
return [
Preparation,
GetCurrentBleedingEdgeVersion,
LastChangeBailout,
FetchLKGR,
GetLKGRVersion,
LKGRVersionUpToDateBailout,
GetTrunkVersion,
CalculateVersion,
CheckTreeStatus,
ChangeVersion,
]
if __name__ == "__main__": # pragma: no cover
sys.exit(BumpUpVersion(CONFIG).Run())

View File

@ -171,6 +171,16 @@ def MakeChangeLogBugReference(body):
return ""
def SortingKey(version):
"""Key for sorting version number strings: '3.11' > '3.2.1.1'"""
version_keys = map(int, version.split("."))
# Fill up to full version numbers to normalize comparison.
while len(version_keys) < 4: # pragma: no cover
version_keys.append(0)
# Fill digits.
return ".".join(map("{0:04d}".format, version_keys))
# Some commands don't like the pipe, e.g. calling vi from within the script or
# from subscripts like git cl upload.
def Command(cmd, args="", prefix="", pipe=True):
@ -369,7 +379,7 @@ class Step(GitRecipesMixin):
def DeleteBranch(self, name):
for line in self.GitBranch().splitlines():
if re.match(r".*\s+%s$" % name, line):
if re.match(r"\*?\s*%s$" % re.escape(name), line):
msg = "Branch %s exists, do you want to delete it?" % name
if self.Confirm(msg):
self.GitDeleteBranch(name)
@ -467,6 +477,25 @@ class Step(GitRecipesMixin):
return self.GitLog(n=1, format="%H", grep=push_pattern,
parent_hash=parent_hash, branch=branch)
def ArrayToVersion(self, prefix):
return ".".join([self[prefix + "major"],
self[prefix + "minor"],
self[prefix + "build"],
self[prefix + "patch"]])
def SetVersion(self, version_file, prefix):
output = ""
for line in FileToText(version_file).splitlines():
if line.startswith("#define MAJOR_VERSION"):
line = re.sub("\d+$", self[prefix + "major"], line)
elif line.startswith("#define MINOR_VERSION"):
line = re.sub("\d+$", self[prefix + "minor"], line)
elif line.startswith("#define BUILD_NUMBER"):
line = re.sub("\d+$", self[prefix + "build"], line)
elif line.startswith("#define PATCH_LEVEL"):
line = re.sub("\d+$", self[prefix + "patch"], line)
output += "%s\n" % line
TextToFile(output, version_file)
class UploadStep(Step):
MESSAGE = "Upload for code review."

View File

@ -68,6 +68,9 @@ class GitRecipesMixin(object):
assert name
self.Git(MakeArgs(["reset --hard", name]))
def GitStash(self):
self.Git(MakeArgs(["stash"]))
def GitRemotes(self):
return map(str.strip, self.Git(MakeArgs(["branch -r"])).splitlines())

View File

@ -124,6 +124,20 @@ class DetectLastPush(Step):
self["last_push_bleeding_edge"] = last_push_bleeding_edge
# TODO(machenbach): Code similarities with bump_up_version.py. Merge after
# turning this script into a pure git script.
class GetCurrentBleedingEdgeVersion(Step):
MESSAGE = "Get latest bleeding edge version."
def RunStep(self):
self.GitCheckoutFile(self.Config(VERSION_FILE), "master")
# Store latest version.
self.ReadAndPersistVersion("latest_")
self["latest_version"] = self.ArrayToVersion("latest_")
print "Bleeding edge version: %s" % self["latest_version"]
class IncrementVersion(Step):
MESSAGE = "Increment version number."
@ -131,11 +145,23 @@ class IncrementVersion(Step):
# Retrieve current version from last trunk push.
self.GitCheckoutFile(self.Config(VERSION_FILE), self["last_push_trunk"])
self.ReadAndPersistVersion()
self["trunk_version"] = self.ArrayToVersion("")
if self["latest_build"] == "9999": # pragma: no cover
# If version control on bleeding edge was switched off, just use the last
# trunk version.
self["latest_version"] = self["trunk_version"]
if SortingKey(self["trunk_version"]) < SortingKey(self["latest_version"]):
# If the version on bleeding_edge is newer than on trunk, use it.
self.GitCheckoutFile(self.Config(VERSION_FILE), "master")
self.ReadAndPersistVersion()
if self.Confirm(("Automatically increment BUILD_NUMBER? (Saying 'n' will "
"fire up your EDITOR on %s so you can make arbitrary "
"changes. When you're done, save the file and exit your "
"EDITOR.)" % self.Config(VERSION_FILE))):
text = FileToText(self.Config(VERSION_FILE))
text = MSub(r"(?<=#define BUILD_NUMBER)(?P<space>\s+)\d*$",
r"\g<space>%s" % str(int(self["build"]) + 1),
@ -147,6 +173,10 @@ class IncrementVersion(Step):
# Variables prefixed with 'new_' contain the new version numbers for the
# ongoing trunk push.
self.ReadAndPersistVersion("new_")
# Make sure patch level is 0 in a new push.
self["new_patch"] = "0"
self["version"] = "%s.%s.%s" % (self["new_major"],
self["new_minor"],
self["new_build"])
@ -307,20 +337,7 @@ class SetVersion(Step):
# The version file has been modified by the patch. Reset it to the version
# on trunk and apply the correct version.
self.GitCheckoutFile(self.Config(VERSION_FILE), "svn/trunk")
output = ""
for line in FileToText(self.Config(VERSION_FILE)).splitlines():
if line.startswith("#define MAJOR_VERSION"):
line = re.sub("\d+$", self["new_major"], line)
elif line.startswith("#define MINOR_VERSION"):
line = re.sub("\d+$", self["new_minor"], line)
elif line.startswith("#define BUILD_NUMBER"):
line = re.sub("\d+$", self["new_build"], line)
elif line.startswith("#define PATCH_LEVEL"):
line = re.sub("\d+$", "0", line)
elif line.startswith("#define IS_CANDIDATE_VERSION"):
line = re.sub("\d+$", "0", line)
output += "%s\n" % line
TextToFile(output, self.Config(VERSION_FILE))
self.SetVersion(self.Config(VERSION_FILE), "new_")
class CommitTrunk(Step):
@ -428,6 +445,7 @@ class PushToTrunk(ScriptsBase):
FreshBranch,
PreparePushRevision,
DetectLastPush,
GetCurrentBleedingEdgeVersion,
IncrementVersion,
PrepareChangeLog,
EditChangeLog,

View File

@ -53,16 +53,6 @@ DEPS_RE = re.compile(r'^\s*(?:"v8_revision": "'
'([0-9]+)".*$', re.M)
def SortingKey(version):
"""Key for sorting version number strings: '3.11' > '3.2.1.1'"""
version_keys = map(int, version.split("."))
# Fill up to full version numbers to normalize comparison.
while len(version_keys) < 4:
version_keys.append(0)
# Fill digits.
return ".".join(map("{0:03d}".format, version_keys))
def SortBranches(branches):
"""Sort branches with version number names."""
return sorted(branches, key=SortingKey, reverse=True)

View File

@ -48,6 +48,10 @@ from chromium_roll import DEPS_FILE
from chromium_roll import ChromiumRoll
import releases
from releases import Releases
import bump_up_version
from bump_up_version import BumpUpVersion
from bump_up_version import LastChangeBailout
from bump_up_version import LKGRVersionUpToDateBailout
TEST_CONFIG = {
@ -352,7 +356,7 @@ class ScriptTest(unittest.TestCase):
def RunStep(self, script=PushToTrunk, step_class=Step, args=None):
"""Convenience wrapper."""
args = args or ["-m"]
args = args if args is not None else ["-m"]
return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
def GitMock(self, cmd, args="", pipe=True):
@ -597,13 +601,19 @@ class ScriptTest(unittest.TestCase):
self.assertEquals("New\n Lines",
FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE]))
# Version on trunk: 3.22.4.0. Version on master (bleeding_edge): 3.22.6.
# Make sure that the increment is 3.22.7.0.
def testIncrementVersion(self):
TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
self.WriteFakeVersionFile()
self._state["last_push_trunk"] = "hash1"
self._state["latest_build"] = "6"
self._state["latest_version"] = "3.22.6.0"
self.ExpectGit([
Git("checkout -f hash1 -- %s" % TEST_CONFIG[VERSION_FILE], "")
Git("checkout -f hash1 -- %s" % TEST_CONFIG[VERSION_FILE], ""),
Git("checkout -f master -- %s" % TEST_CONFIG[VERSION_FILE], "",
cb=lambda: self.WriteFakeVersionFile(22, 6)),
])
self.ExpectReadline([
@ -614,7 +624,7 @@ class ScriptTest(unittest.TestCase):
self.assertEquals("3", self._state["new_major"])
self.assertEquals("22", self._state["new_minor"])
self.assertEquals("5", self._state["new_build"])
self.assertEquals("7", self._state["new_build"])
self.assertEquals("0", self._state["new_patch"])
def _TestSquashCommits(self, change_log, expected_msg):
@ -741,6 +751,8 @@ Performance and stability improvements on all platforms.""", commit)
Git("log -1 --format=%s hash2",
"Version 3.4.5 (based on bleeding_edge revision r1234)\n"),
Git("svn find-rev r1234", "hash3\n"),
Git("checkout -f master -- %s" % TEST_CONFIG[VERSION_FILE], "",
cb=self.WriteFakeVersionFile),
Git("checkout -f hash2 -- %s" % TEST_CONFIG[VERSION_FILE], "",
cb=self.WriteFakeVersionFile),
Git("log --format=%H hash3..push_hash", "rev1\n"),
@ -1266,6 +1278,92 @@ LOG=N
self.assertEquals(expected_json, json.loads(FileToText(json_output)))
def testBumpUpVersion(self):
TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
self.WriteFakeVersionFile()
def ResetVersion(minor, build, patch=0):
return lambda: self.WriteFakeVersionFile(minor=minor,
build=build,
patch=patch)
self.ExpectGit([
Git("status -s -uno", ""),
Git("checkout -f bleeding_edge", "", cb=ResetVersion(11, 4)),
Git("pull", ""),
Git("branch", ""),
Git("checkout -f bleeding_edge", ""),
Git("log -1 --format=%H", "latest_hash"),
Git("diff --name-only latest_hash latest_hash^", ""),
Git("checkout -f bleeding_edge", ""),
Git("log --format=%H --grep=\"^git-svn-id: [^@]*@12345 [A-Za-z0-9-]*$\"",
"lkgr_hash"),
Git("checkout -b auto-bump-up-version lkgr_hash", ""),
Git("checkout -f bleeding_edge", ""),
Git("branch", ""),
Git("diff --name-only lkgr_hash lkgr_hash^", ""),
Git("checkout -f master", "", cb=ResetVersion(11, 5)),
Git("pull", ""),
Git("checkout -b auto-bump-up-version bleeding_edge", "",
cb=ResetVersion(11, 4)),
Git("commit -am \"[Auto-roll] Bump up version to 3.11.6.0\n\n"
"TBR=author@chromium.org\"", ""),
Git("cl upload --send-mail --email \"author@chromium.org\" -f", ""),
Git("cl dcommit -f --bypass-hooks", ""),
Git("checkout -f bleeding_edge", ""),
Git("branch", "auto-bump-up-version\n* bleeding_edge"),
Git("branch -D auto-bump-up-version", ""),
])
self.ExpectReadURL([
URL("https://v8-status.appspot.com/lkgr", "12345"),
URL("https://v8-status.appspot.com/current?format=json",
"{\"message\": \"Tree is open\"}"),
])
BumpUpVersion(TEST_CONFIG, self).Run(["-a", "author@chromium.org"])
# Test that we bail out if the last change was a version change.
def testBumpUpVersionBailout1(self):
TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
self._state["latest"] = "latest_hash"
self.ExpectGit([
Git("diff --name-only latest_hash latest_hash^",
TEST_CONFIG[VERSION_FILE]),
])
self.assertEquals(1,
self.RunStep(BumpUpVersion, LastChangeBailout, ["--dry_run"]))
# Test that we bail out if the lkgr was a version change.
def testBumpUpVersionBailout2(self):
TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
self._state["lkgr"] = "lkgr_hash"
self.ExpectGit([
Git("diff --name-only lkgr_hash lkgr_hash^", TEST_CONFIG[VERSION_FILE]),
])
self.assertEquals(1,
self.RunStep(BumpUpVersion, LKGRVersionUpToDateBailout, ["--dry_run"]))
# Test that we bail out if the last version is already newer than the lkgr's
# version.
def testBumpUpVersionBailout3(self):
TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
self._state["lkgr"] = "lkgr_hash"
self._state["lkgr_version"] = "3.22.4.0"
self._state["latest_version"] = "3.22.5.0"
self.ExpectGit([
Git("diff --name-only lkgr_hash lkgr_hash^", ""),
])
self.assertEquals(1,
self.RunStep(BumpUpVersion, LKGRVersionUpToDateBailout, ["--dry_run"]))
class SystemTest(unittest.TestCase):
def testReload(self):
step = MakeStep(step_class=PrepareChangeLog, number=0, state={}, config={},