Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

20
deps/v8/tools/release/PRESUBMIT.py vendored Normal file
View File

@ -0,0 +1,20 @@
# Copyright 2017 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.
# This line is 'magic' in that git-cl looks for it to decide whether to
# use Python3 instead of Python2 when running the code in this file.
USE_PYTHON3 = True
def _CommonChecks(input_api, output_api):
tests = input_api.canned_checks.GetUnitTestsInDirectory(
input_api, output_api, '.', files_to_check=['test_scripts.py$'],
run_on_python2=False)
return input_api.RunTests(tests)
def CheckChangeOnUpload(input_api, output_api):
return _CommonChecks(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api):
return _CommonChecks(input_api, output_api)

113
deps/v8/tools/release/auto_push.py vendored Executable file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env python3
# Copyright 2013 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
from common_includes import *
import create_release
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
# Fetch unfetched revisions.
self.vc.Fetch()
class FetchCandidate(Step):
MESSAGE = "Fetching V8 lkgr ref."
def RunStep(self):
# The roll ref points to the candidate to be rolled.
self.Git("fetch origin +refs/heads/lkgr:refs/heads/lkgr")
self["candidate"] = self.Git("show-ref -s refs/heads/lkgr").strip()
class LastReleaseBailout(Step):
MESSAGE = "Checking last V8 release base."
def RunStep(self):
last_release = self.GetLatestReleaseBase()
commits = self.GitLog(
format="%H", git_hash="%s..%s" % (last_release, self["candidate"]))
if not commits:
print("Already pushed current candidate %s" % self["candidate"])
return True
class CreateRelease(Step):
MESSAGE = "Creating release if specified."
def RunStep(self):
print("Creating release for %s." % self["candidate"])
args = [
"--author", self._options.author,
"--reviewer", self._options.reviewer,
"--revision", self["candidate"],
"--force",
]
if self._options.work_dir:
args.extend(["--work-dir", self._options.work_dir])
if self._options.push:
self._side_effect_handler.Call(
create_release.CreateRelease().Run, args)
class AutoPush(ScriptsBase):
def _PrepareOptions(self, parser):
parser.add_argument("-p", "--push",
help="Create release. Dry run if unspecified.",
default=False, action="store_true")
def _ProcessOptions(self, options):
if not options.author or not options.reviewer: # pragma: no cover
print("You need to specify author and reviewer.")
return False
options.requires_editor = False
return True
def _Config(self):
return {
"PERSISTFILE_BASENAME": "/tmp/v8-auto-push-tempfile",
}
def _Steps(self):
return [
Preparation,
FetchCandidate,
LastReleaseBailout,
CreateRelease,
]
if __name__ == "__main__": # pragma: no cover
sys.exit(AutoPush().Run())

809
deps/v8/tools/release/common_includes.py vendored Normal file
View File

@ -0,0 +1,809 @@
#!/usr/bin/env python3
# Copyright 2013 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import datetime
from distutils.version import LooseVersion
import glob
import json
import os
import re
import shutil
import subprocess
import sys
import time
import urllib
from git_recipes import GitRecipesMixin
from git_recipes import GitFailedException
import http.client as httplib
import urllib.request as urllib2
DAY_IN_SECONDS = 24 * 60 * 60
PUSH_MSG_GIT_RE = re.compile(r".* \(based on (?P<git_rev>[a-fA-F0-9]+)\)$")
PUSH_MSG_NEW_RE = re.compile(r"^Version \d+\.\d+\.\d+$")
VERSION_FILE = os.path.join("include", "v8-version.h")
WATCHLISTS_FILE = "WATCHLISTS"
RELEASE_WORKDIR = "/tmp/v8-release-scripts-work-dir/"
# V8 base directory.
V8_BASE = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Add our copy of depot_tools to the PATH as many scripts use tools from there,
# e.g. git-cl, fetch, git-new-branch etc, and we can not depend on depot_tools
# being in the PATH on the LUCI bots.
path_to_depot_tools = os.path.join(V8_BASE, 'third_party', 'depot_tools')
new_path = path_to_depot_tools + os.pathsep + os.environ.get('PATH')
os.environ['PATH'] = new_path
def TextToFile(text, file_name):
with open(file_name, "w") as f:
f.write(text)
def AppendToFile(text, file_name):
with open(file_name, "a") as f:
f.write(text)
def LinesInFile(file_name):
with open(file_name) as f:
for line in f:
yield line
def FileToText(file_name):
with open(file_name) as f:
return f.read()
def MSub(rexp, replacement, text):
return re.sub(rexp, replacement, text, flags=re.MULTILINE)
# 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, cwd=None):
cwd = cwd or os.getcwd()
# TODO(machenbach): Use timeout.
cmd_line = "%s %s %s" % (prefix, cmd, args)
print("Command: %s" % cmd_line)
print("in %s" % cwd)
sys.stdout.flush()
try:
if pipe:
return subprocess.check_output(cmd_line, shell=True, cwd=cwd).decode('utf-8')
else:
return subprocess.check_call(cmd_line, shell=True, cwd=cwd)
except subprocess.CalledProcessError:
return None
finally:
sys.stdout.flush()
sys.stderr.flush()
def SanitizeVersionTag(tag):
version_without_prefix = re.compile(r"^\d+\.\d+\.\d+(?:\.\d+)?$")
version_with_prefix = re.compile(r"^tags\/\d+\.\d+\.\d+(?:\.\d+)?$")
if version_without_prefix.match(tag):
return tag
elif version_with_prefix.match(tag):
return tag[len("tags/"):]
else:
return None
def NormalizeVersionTags(version_tags):
normalized_version_tags = []
# Remove tags/ prefix because of packed refs.
for current_tag in version_tags:
version_tag = SanitizeVersionTag(current_tag)
if version_tag is not None:
normalized_version_tags.append(version_tag)
return normalized_version_tags
# Wrapper for side effects.
class SideEffectHandler(object): # pragma: no cover
def Call(self, fun, *args, **kwargs):
return fun(*args, **kwargs)
def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
return Command(cmd, args, prefix, pipe, cwd=cwd)
def ReadLine(self):
sys.stdout.flush()
return sys.stdin.readline().strip()
def ReadURL(self, url, params=None):
# pylint: disable=E1121
url_fh = urllib2.urlopen(url, params, 60)
try:
return url_fh.read()
finally:
url_fh.close()
def ReadClusterFuzzAPI(self, api_key, **params):
params["api_key"] = api_key.strip()
params = urllib.urlencode(params)
headers = {"Content-type": "application/x-www-form-urlencoded"}
conn = httplib.HTTPSConnection("backend-dot-cluster-fuzz.appspot.com")
conn.request("POST", "/_api/", params, headers)
response = conn.getresponse()
data = response.read()
try:
return json.loads(data)
except:
print(data)
print("ERROR: Could not read response. Is your key valid?")
raise
def Sleep(self, seconds):
time.sleep(seconds)
def GetUTCStamp(self):
return time.mktime(datetime.datetime.utcnow().timetuple())
DEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler()
class NoRetryException(Exception):
pass
class VCInterface(object):
def InjectStep(self, step):
self.step=step
def Pull(self):
raise NotImplementedError()
def Fetch(self):
raise NotImplementedError()
def GetTags(self):
raise NotImplementedError()
def GetBranches(self):
raise NotImplementedError()
def MainBranch(self):
raise NotImplementedError()
def CandidateBranch(self):
raise NotImplementedError()
def RemoteMainBranch(self):
raise NotImplementedError()
def RemoteCandidateBranch(self):
raise NotImplementedError()
def RemoteBranch(self, name):
raise NotImplementedError()
def CLLand(self):
raise NotImplementedError()
def Tag(self, tag, remote, message):
"""Sets a tag for the current commit.
Assumptions: The commit already landed and the commit message is unique.
"""
raise NotImplementedError()
class GitInterface(VCInterface):
def Pull(self):
self.step.GitPull()
def Fetch(self):
self.step.Git("fetch")
def GetTags(self):
return self.step.Git("tag").strip().splitlines()
def GetBranches(self):
# Get relevant remote branches, e.g. "branch-heads/3.25".
branches = filter(
lambda s: re.match(r"^branch\-heads/\d+\.\d+$", s),
self.step.GitRemotes())
# Remove 'branch-heads/' prefix.
return [b[13:] for b in branches]
def MainBranch(self):
return "main"
def CandidateBranch(self):
return "candidates"
def RemoteMainBranch(self):
return "origin/main"
def RemoteCandidateBranch(self):
return "origin/candidates"
def RemoteBranch(self, name):
# Assume that if someone "fully qualified" the ref, they know what they
# want.
if name.startswith('refs/'):
return name
if name in ["candidates", "main"]:
return "refs/remotes/origin/%s" % name
try:
# Check if branch is in heads.
if self.step.Git("show-ref refs/remotes/origin/%s" % name).strip():
return "refs/remotes/origin/%s" % name
except GitFailedException:
pass
try:
# Check if branch is in branch-heads.
if self.step.Git("show-ref refs/remotes/branch-heads/%s" % name).strip():
return "refs/remotes/branch-heads/%s" % name
except GitFailedException:
pass
self.Die("Can't find remote of %s" % name)
def Tag(self, tag, remote, message):
# Wait for the commit to appear. Assumes unique commit message titles (this
# is the case for all automated merge and push commits - also no title is
# the prefix of another title).
commit = None
for wait_interval in [10, 30, 60, 60, 60, 60, 60]:
self.step.Git("fetch")
commit = self.step.GitLog(n=1, format="%H", grep=message, branch=remote)
if commit:
break
print("The commit has not replicated to git. Waiting for %s seconds." %
wait_interval)
self.step._side_effect_handler.Sleep(wait_interval)
else:
self.step.Die("Couldn't determine commit for setting the tag. Maybe the "
"git updater is lagging behind?")
self.step.Git("tag %s %s" % (tag, commit))
self.step.Git("push origin refs/tags/%s:refs/tags/%s" % (tag, tag))
def CLLand(self):
self.step.GitCLLand()
class Step(GitRecipesMixin):
def __init__(self, text, number, config, state, options, handler):
self._text = text
self._number = number
self._config = config
self._state = state
self._options = options
self._side_effect_handler = handler
self.vc = GitInterface()
self.vc.InjectStep(self)
# The testing configuration might set a different default cwd.
self.default_cwd = (self._config.get("DEFAULT_CWD") or
os.path.join(self._options.work_dir, "v8"))
assert self._number >= 0
assert self._config is not None
assert self._state is not None
assert self._side_effect_handler is not None
def __getitem__(self, key):
# Convenience method to allow direct [] access on step classes for
# manipulating the backed state dict.
return self._state.get(key)
def __setitem__(self, key, value):
# Convenience method to allow direct [] access on step classes for
# manipulating the backed state dict.
self._state[key] = value
def Config(self, key):
return self._config[key]
def Run(self):
# Restore state.
state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
if not self._state and os.path.exists(state_file):
self._state.update(json.loads(FileToText(state_file)))
print(">>> Step %d: %s" % (self._number, self._text))
try:
return self.RunStep()
finally:
# Persist state.
TextToFile(json.dumps(self._state), state_file)
def RunStep(self): # pragma: no cover
raise NotImplementedError
def Retry(self, cb, retry_on=None, wait_plan=None):
""" Retry a function.
Params:
cb: The function to retry.
retry_on: A callback that takes the result of the function and returns
True if the function should be retried. A function throwing an
exception is always retried.
wait_plan: A list of waiting delays between retries in seconds. The
maximum number of retries is len(wait_plan).
"""
retry_on = retry_on or (lambda x: False)
wait_plan = list(wait_plan or [])
wait_plan.reverse()
while True:
got_exception = False
try:
result = cb()
except NoRetryException as e:
raise e
except Exception as e:
got_exception = e
if got_exception or retry_on(result):
if not wait_plan: # pragma: no cover
raise Exception("Retried too often. Giving up. Reason: %s" %
str(got_exception))
wait_time = wait_plan.pop()
print("Waiting for %f seconds." % wait_time)
self._side_effect_handler.Sleep(wait_time)
print("Retrying...")
else:
return result
def ReadLine(self, default=None):
# Don't prompt in forced mode.
if self._options.force_readline_defaults and default is not None:
print("%s (forced)" % default)
return default
else:
return self._side_effect_handler.ReadLine()
def Command(self, name, args, cwd=None):
cmd = lambda: self._side_effect_handler.Command(
name, args, "", True, cwd=cwd or self.default_cwd)
return self.Retry(cmd, None, [5])
def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
cmd = lambda: self._side_effect_handler.Command(
"git", args, prefix, pipe, cwd=cwd or self.default_cwd)
result = self.Retry(cmd, retry_on, [5, 30])
if result is None:
raise GitFailedException("'git %s' failed." % args)
return result
def Editor(self, args):
if self._options.requires_editor:
return self._side_effect_handler.Command(
os.environ["EDITOR"],
args,
pipe=False,
cwd=self.default_cwd)
def ReadURL(self, url, params=None, retry_on=None, wait_plan=None):
wait_plan = wait_plan or [3, 60, 600]
cmd = lambda: self._side_effect_handler.ReadURL(url, params)
return self.Retry(cmd, retry_on, wait_plan)
def Die(self, msg=""):
if msg != "":
print("Error: %s" % msg)
print("Exiting")
raise Exception(msg)
def DieNoManualMode(self, msg=""):
if not self._options.manual: # pragma: no cover
msg = msg or "Only available in manual mode."
self.Die(msg)
def Confirm(self, msg):
print("%s [Y/n] " % msg, end=' ')
answer = self.ReadLine(default="Y")
return answer == "" or answer == "Y" or answer == "y"
def DeleteBranch(self, name, cwd=None):
for line in self.GitBranch(cwd=cwd).splitlines():
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, cwd=cwd)
print("Branch %s deleted." % name)
else:
msg = "Can't continue. Please delete branch %s and try again." % name
self.Die(msg)
def InitialEnvironmentChecks(self, cwd):
# Cancel if this is not a git checkout.
if not os.path.exists(os.path.join(cwd, ".git")): # pragma: no cover
self.Die("%s is not a git checkout. If you know what you're doing, try "
"deleting it and rerunning this script." % cwd)
# Cancel if EDITOR is unset or not executable.
if (self._options.requires_editor and (not os.environ.get("EDITOR") or
self.Command(
"which", os.environ["EDITOR"]) is None)): # pragma: no cover
self.Die("Please set your EDITOR environment variable, you'll need it.")
def CommonPrepare(self):
# Check for a clean workdir.
if not self.GitIsWorkdirClean(): # pragma: no cover
self.Die("Workspace is not clean. Please commit or undo your changes.")
# Checkout main in case the script was left on a work branch.
self.GitCheckout('origin/main')
# Fetch unfetched revisions.
self.vc.Fetch()
def PrepareBranch(self):
# Delete the branch that will be created later if it exists already.
self.DeleteBranch(self._config["BRANCHNAME"])
def CommonCleanup(self):
self.GitCheckout('origin/main')
self.GitDeleteBranch(self._config["BRANCHNAME"])
# Clean up all temporary files.
for f in glob.iglob("%s*" % self._config["PERSISTFILE_BASENAME"]):
if os.path.isfile(f):
os.remove(f)
if os.path.isdir(f):
shutil.rmtree(f)
def ReadAndPersistVersion(self, prefix=""):
def ReadAndPersist(var_name, def_name):
match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
if match:
value = match.group(1)
self["%s%s" % (prefix, var_name)] = value
for line in LinesInFile(os.path.join(self.default_cwd, VERSION_FILE)):
for (var_name, def_name) in [("major", "V8_MAJOR_VERSION"),
("minor", "V8_MINOR_VERSION"),
("build", "V8_BUILD_NUMBER"),
("patch", "V8_PATCH_LEVEL")]:
ReadAndPersist(var_name, def_name)
def WaitForLGTM(self):
print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
"your change. (If you need to iterate on the patch or double check "
"that it's sensible, do so in another shell, but remember to not "
"change the headline of the uploaded CL.")
answer = ""
while answer != "LGTM":
print("> ", end=' ')
answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
if answer != "LGTM":
print("That was not 'LGTM'.")
def WaitForResolvingConflicts(self, patch_file):
print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
"or resolve the conflicts, stage *all* touched files with "
"'git add', and type \"RESOLVED<Return>\"" % (patch_file))
self.DieNoManualMode()
answer = ""
while answer != "RESOLVED":
if answer == "ABORT":
self.Die("Applying the patch failed.")
if answer != "":
print("That was not 'RESOLVED' or 'ABORT'.")
print("> ", end=' ')
answer = self.ReadLine()
# Takes a file containing the patch to apply as first argument.
def ApplyPatch(self, patch_file, revert=False):
try:
self.GitApplyPatch(patch_file, revert)
except GitFailedException:
self.WaitForResolvingConflicts(patch_file)
def GetVersionTag(self, revision):
tags = self.Git(f"tag --points-at {revision}").strip().split('\n')
for tag in tags:
sanitized_tag = SanitizeVersionTag(tag)
if sanitized_tag:
return sanitized_tag
return None
def GetLatestVersion(self):
# Use cached version if available.
if self["latest_version"]:
return self["latest_version"]
# Make sure tags are fetched.
self.Git("fetch origin +refs/tags/*:refs/tags/*")
all_tags = self.vc.GetTags()
only_version_tags = NormalizeVersionTags(all_tags)
def patched_dev_version(tag):
"""True if this tag represents a patched dev or mini branch."""
parts = tag.split('.')
return len(parts) >= 3 and int(parts[2]) > 999
filtered_version_tags = [
tag for tag in only_version_tags if not patched_dev_version(tag)]
version = sorted(filtered_version_tags,
key=LooseVersion, reverse=True)[0]
self["latest_version"] = version
return version
def GetLatestRelease(self):
"""The latest release is the git hash of the latest tagged version.
This revision should be rolled into chromium.
"""
latest_version = self.GetLatestVersion()
# The latest release.
latest_hash = self.GitLog(n=1, format="%H", branch=latest_version)
assert latest_hash
return latest_hash
def GetLatestReleaseBase(self, version=None):
"""The latest release base is the latest revision that is covered in the
last change log file. It doesn't include cherry-picked patches.
"""
latest_version = version or self.GetLatestVersion()
# Strip patch level if it exists.
latest_version = ".".join(latest_version.split(".")[:3])
# The latest release base.
latest_hash = self.GitLog(n=1, format="%H", branch=latest_version)
assert latest_hash
title = self.GitLog(n=1, format="%s", git_hash=latest_hash)
match = PUSH_MSG_GIT_RE.match(title)
if match:
# Legacy: In the old process there's one level of indirection. The
# version is on the candidates branch and points to the real release
# base on main through the commit message.
return match.group("git_rev")
match = PUSH_MSG_NEW_RE.match(title)
if match:
# This is a new-style v8 version branched from main. The commit
# "latest_hash" is the version-file change. Its parent is the release
# base on main.
return self.GitLog(n=1, format="%H", git_hash="%s^" % latest_hash)
self.Die("Unknown latest release: %s" % latest_hash)
def ArrayToVersion(self, prefix):
return ".".join([self[prefix + "major"],
self[prefix + "minor"],
self[prefix + "build"],
self[prefix + "patch"]])
def StoreVersion(self, version, prefix):
version_parts = version.split(".")
if len(version_parts) == 3:
version_parts.append("0")
major, minor, build, patch = version_parts
self[prefix + "major"] = major
self[prefix + "minor"] = minor
self[prefix + "build"] = build
self[prefix + "patch"] = patch
def SetVersion(self, version_file, prefix):
output = ""
for line in FileToText(version_file).splitlines():
if line.startswith("#define V8_MAJOR_VERSION"):
line = re.sub("\d+$", self[prefix + "major"], line)
elif line.startswith("#define V8_MINOR_VERSION"):
line = re.sub("\d+$", self[prefix + "minor"], line)
elif line.startswith("#define V8_BUILD_NUMBER"):
line = re.sub("\d+$", self[prefix + "build"], line)
elif line.startswith("#define V8_PATCH_LEVEL"):
line = re.sub("\d+$", self[prefix + "patch"], line)
elif (self[prefix + "candidate"] and
line.startswith("#define V8_IS_CANDIDATE_VERSION")):
line = re.sub("\d+$", self[prefix + "candidate"], line)
output += "%s\n" % line
TextToFile(output, version_file)
class BootstrapStep(Step):
MESSAGE = "Bootstrapping checkout and state."
def RunStep(self):
# Reserve state entry for json output.
self['json_output'] = {}
if os.path.realpath(self.default_cwd) == os.path.realpath(V8_BASE):
self.Die("Can't use v8 checkout with calling script as work checkout.")
# Directory containing the working v8 checkout.
if not os.path.exists(self._options.work_dir):
os.makedirs(self._options.work_dir)
if not os.path.exists(self.default_cwd):
self.Command("fetch", "v8", cwd=self._options.work_dir)
class UploadStep(Step):
MESSAGE = "Upload for code review."
def RunStep(self):
reviewer = None
if self._options.reviewer:
print("Using account %s for review." % self._options.reviewer)
reviewer = self._options.reviewer
tbr_reviewer = None
if self._options.tbr_reviewer:
print("Using account %s for TBR review." % self._options.tbr_reviewer)
tbr_reviewer = self._options.tbr_reviewer
if not reviewer and not tbr_reviewer:
print(
"Please enter the email address of a V8 reviewer for your patch: ",
end=' ')
self.DieNoManualMode("A reviewer must be specified in forced mode.")
reviewer = self.ReadLine()
self.GitUpload(reviewer, self._options.force_upload,
bypass_hooks=self._options.bypass_upload_hooks,
tbr_reviewer=tbr_reviewer)
def MakeStep(step_class=Step, number=0, state=None, config=None,
options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
# Allow to pass in empty dictionaries.
state = state if state is not None else {}
config = config if config is not None else {}
try:
message = step_class.MESSAGE
except AttributeError:
message = step_class.__name__
return step_class(
message,
number=number,
config=config,
state=state,
options=options,
handler=side_effect_handler)
class ScriptsBase(object):
def __init__(self,
config=None,
side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
state=None):
self._config = config or self._Config()
self._side_effect_handler = side_effect_handler
self._state = state if state is not None else {}
def _Description(self):
return None
def _PrepareOptions(self, parser):
pass
def _ProcessOptions(self, options):
return True
def _Steps(self): # pragma: no cover
raise Exception("Not implemented.")
def _Config(self):
return {}
def MakeOptions(self, args=None):
parser = argparse.ArgumentParser(description=self._Description())
parser.add_argument("-a", "--author", default="",
help="The author email used for code review.")
parser.add_argument("--dry-run", default=False, action="store_true",
help="Perform only read-only actions.")
parser.add_argument("--json-output",
help="File to write results summary to.")
parser.add_argument("-r", "--reviewer", default="",
help="The account name to be used for reviews.")
parser.add_argument("--tbr-reviewer", "--tbr", default="",
help="The account name to be used for TBR reviews.")
parser.add_argument("-s", "--step",
help="Specify the step where to start work. Default: 0.",
default=0, type=int)
parser.add_argument("--work-dir",
help=("Location where to bootstrap a working v8 "
"checkout."))
self._PrepareOptions(parser)
if args is None: # pragma: no cover
options = parser.parse_args()
else:
options = parser.parse_args(args)
# Process common options.
if options.step < 0: # pragma: no cover
print("Bad step number %d" % options.step)
parser.print_help()
return None
# Defaults for options, common to all scripts.
options.manual = getattr(options, "manual", True)
options.force = getattr(options, "force", False)
options.bypass_upload_hooks = False
# Derived options.
options.requires_editor = not options.force
options.wait_for_lgtm = not options.force
options.force_readline_defaults = not options.manual
options.force_upload = not options.manual
# Process script specific options.
if not self._ProcessOptions(options):
parser.print_help()
return None
if not options.work_dir:
options.work_dir = "/tmp/v8-release-scripts-work-dir"
return options
def RunSteps(self, step_classes, args=None):
options = self.MakeOptions(args)
if not options:
return 1
# Ensure temp dir exists for state files.
state_dir = os.path.dirname(self._config["PERSISTFILE_BASENAME"])
if not os.path.exists(state_dir):
os.makedirs(state_dir)
state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
if options.step == 0 and os.path.exists(state_file):
os.remove(state_file)
steps = []
for (number, step_class) in enumerate([BootstrapStep] + step_classes):
steps.append(MakeStep(step_class, number, self._state, self._config,
options, self._side_effect_handler))
try:
for step in steps[options.step:]:
if step.Run():
return 0
finally:
if options.json_output:
with open(options.json_output, "w") as f:
json.dump(self._state['json_output'], f)
return 0
def Run(self, args=None):
return self.RunSteps(self._Steps(), args)

230
deps/v8/tools/release/create_release.py vendored Executable file
View File

@ -0,0 +1,230 @@
#!/usr/bin/env python3
# Copyright 2015 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.
import os
import sys
from common_includes import *
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
self.Git("fetch origin +refs/heads/*:refs/heads/*")
self.GitCheckout("origin/main")
self.DeleteBranch("work-branch")
class PrepareBranchRevision(Step):
MESSAGE = "Check from which revision to branch off."
def RunStep(self):
self["push_hash"] = (self._options.revision or
self.GitLog(n=1, format="%H", branch="origin/main"))
assert self["push_hash"]
print("Release revision %s" % self["push_hash"])
class IncrementVersion(Step):
MESSAGE = "Increment version number."
def RunStep(self):
latest_version = self.GetLatestVersion()
# The version file on main can be used to bump up major/minor at
# branch time.
self.GitCheckoutFile(VERSION_FILE, self.vc.RemoteMainBranch())
self.ReadAndPersistVersion("main_")
main_version = self.ArrayToVersion("main_")
# Use the highest version from main or from tags to determine the new
# version.
authoritative_version = sorted(
[main_version, latest_version], key=LooseVersion)[1]
self.StoreVersion(authoritative_version, "authoritative_")
# Variables prefixed with 'new_' contain the new version numbers for the
# ongoing candidates push.
self["new_major"] = self["authoritative_major"]
self["new_minor"] = self["authoritative_minor"]
self["new_build"] = str(int(self["authoritative_build"]) + 1)
# Make sure patch level is 0 in a new push.
self["new_patch"] = "0"
# The new version is not a candidate.
self["new_candidate"] = "0"
self["version"] = "%s.%s.%s" % (self["new_major"],
self["new_minor"],
self["new_build"])
print ("Incremented version to %s" % self["version"])
class DetectLastRelease(Step):
MESSAGE = "Detect commit ID of last release base."
def RunStep(self):
self["last_push_main"] = self.GetLatestReleaseBase()
class DeleteBranchRef(Step):
MESSAGE = "Delete branch ref."
def RunStep(self):
cmd = "push origin :refs/heads/%s" % self["version"]
if self._options.dry_run:
print("Dry run. Command:\ngit %s" % cmd)
else:
try:
self.Git(cmd)
except Exception:
# Be forgiving if branch ref does not exist.
pass
class PushBranchRef(Step):
MESSAGE = "Create branch ref."
def RunStep(self):
cmd = "push origin %s:refs/heads/%s" % (self["push_hash"], self["version"])
if self._options.dry_run:
print("Dry run. Command:\ngit %s" % cmd)
else:
self.Git(cmd)
class MakeBranch(Step):
MESSAGE = "Create the branch."
def RunStep(self):
self.Git("reset --hard origin/main")
self.Git("new-branch work-branch --upstream origin/%s" % self["version"])
self.GitCheckoutFile(VERSION_FILE, self["latest_version"])
class SetVersion(Step):
MESSAGE = "Set correct version for candidates."
def RunStep(self):
self.SetVersion(os.path.join(self.default_cwd, VERSION_FILE), "new_")
class EnableMergeWatchlist(Step):
MESSAGE = "Enable watchlist entry for merge notifications."
def RunStep(self):
old_watchlist_content = FileToText(os.path.join(self.default_cwd,
WATCHLISTS_FILE))
new_watchlist_content = re.sub("(# 'v8-merges@googlegroups\.com',)",
"'v8-merges@googlegroups.com',",
old_watchlist_content)
TextToFile(new_watchlist_content, os.path.join(self.default_cwd,
WATCHLISTS_FILE))
class CommitBranch(Step):
MESSAGE = "Commit version to new branch."
def RunStep(self):
self["commit_title"] = "Version %s" % self["version"]
text = "%s" % (self["commit_title"])
TextToFile(text, self.Config("COMMITMSG_FILE"))
self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))
class LandBranch(Step):
MESSAGE = "Upload and land changes."
def RunStep(self):
if self._options.dry_run:
print("Dry run - upload CL.")
else:
self.GitUpload(force=True,
bypass_hooks=True,
no_autocc=True,
set_bot_commit=True,
message_file=self.Config("COMMITMSG_FILE"))
# TODO(crbug.com/1176141): This might need to go through CQ.
# We'd need to wait for it to land and then tag it.
cmd = "cl land --bypass-hooks -f"
if self._options.dry_run:
print("Dry run. Command:\ngit %s" % cmd)
else:
self.Git(cmd)
os.remove(self.Config("COMMITMSG_FILE"))
class TagRevision(Step):
MESSAGE = "Tag the new revision."
def RunStep(self):
if self._options.dry_run:
print ("Dry run. Tagging \"%s\" with %s" %
(self["commit_title"], self["version"]))
else:
self.vc.Tag(self["version"],
"origin/%s" % self["version"],
self["commit_title"])
class CleanUp(Step):
MESSAGE = "Done!"
def RunStep(self):
print("Congratulations, you have successfully created version %s."
% self["version"])
self.GitCheckout("origin/main")
self.DeleteBranch("work-branch")
self.Git("gc")
class CreateRelease(ScriptsBase):
def _PrepareOptions(self, parser):
group = parser.add_mutually_exclusive_group()
group.add_argument("-f", "--force",
help="Don't prompt the user.",
default=True, action="store_true")
group.add_argument("-m", "--manual",
help="Prompt the user at every important step.",
default=False, action="store_true")
parser.add_argument("-R", "--revision",
help="The git commit ID to push (defaults to HEAD).")
def _ProcessOptions(self, options): # pragma: no cover
if not options.author or not options.reviewer:
print("Reviewer (-r) and author (-a) are required.")
return False
return True
def _Config(self):
return {
"PERSISTFILE_BASENAME": "/tmp/create-releases-tempfile",
"COMMITMSG_FILE": "/tmp/v8-create-releases-tempfile-commitmsg",
}
def _Steps(self):
return [
Preparation,
PrepareBranchRevision,
IncrementVersion,
DetectLastRelease,
DeleteBranchRef,
PushBranchRef,
MakeBranch,
SetVersion,
EnableMergeWatchlist,
CommitBranch,
LandBranch,
TagRevision,
CleanUp,
]
if __name__ == "__main__": # pragma: no cover
sys.exit(CreateRelease().Run())

107
deps/v8/tools/release/filter_build_files.py vendored Executable file
View File

@ -0,0 +1,107 @@
#!/usr/bin/env python3
# Copyright 2017 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.
"""
Enumerates relevant build files for each platform.
This can be used to filter the build directory before making an official
archive. The archive should only contain files required for running or
static linking, e.g. executables, startup files, libraries.
The script is limited to release builds and assumes GN.
"""
import argparse
import glob
import itertools
import json
import os
import sys
EXECUTABLE_FILES = [
'd8',
]
# Additional executable files added only to ref archive type.
REFBUILD_EXECUTABLE_FILES = [
'cctest',
]
SUPPLEMENTARY_FILES = [
'icudtl.dat',
'snapshot_blob.bin',
'v8_build_config.json',
]
LIBRARY_FILES = {
'android': ['*.a', '*.so'],
'linux': ['*.a', '*.so'],
'mac': ['*.a', '*.so', '*.dylib'],
'win': ['*.lib', '*.dll'],
}
def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-d', '--dir', required=True,
help='Path to the build directory.')
parser.add_argument('-p', '--platform', required=True,
help='Target platform name: win|mac|linux.')
parser.add_argument('-o', '--json-output', required=True,
help='Path to an output file. The files will '
'be stored in json list with absolute paths.')
parser.add_argument('-t', '--type',
choices=['all', 'exe', 'lib', 'ref'], default='all',
help='Specifies the archive type.')
args = parser.parse_args()
if not os.path.isdir(args.dir):
parser.error('%s is not an existing directory.' % args.dir)
args.dir = os.path.abspath(args.dir)
# Skip libraries for exe and ref archive types.
if args.type in ('exe', 'ref'):
library_files = []
else:
library_files = LIBRARY_FILES[args.platform]
# Skip executables for lib archive type.
if args.type == 'lib':
executable_files = []
else:
executable_files = EXECUTABLE_FILES
if args.type == 'ref':
executable_files.extend(REFBUILD_EXECUTABLE_FILES)
list_of_files = []
def add_files_from_globs(globs):
list_of_files.extend(itertools.chain(*map(glob.iglob, globs)))
# Add toplevel executables, supplementary files and libraries.
extended_executable_files = [
f + '.exe' if args.platform == 'win' else f
for f in executable_files]
add_files_from_globs(
os.path.join(args.dir, f)
for f in extended_executable_files +
SUPPLEMENTARY_FILES +
library_files
)
# Add libraries recursively from obj directory.
for root, _, __ in os.walk(os.path.join(args.dir, 'obj'), followlinks=True):
add_files_from_globs(os.path.join(root, g) for g in library_files)
with open(args.json_output, 'w') as f:
json.dump(list_of_files, f)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))

295
deps/v8/tools/release/git_recipes.py vendored Normal file
View File

@ -0,0 +1,295 @@
#!/usr/bin/env python3
# Copyright 2014 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
SHA1_RE = re.compile('^[a-fA-F0-9]{40}$')
ROLL_DEPS_GIT_SVN_ID_RE = re.compile('^git-svn-id: .*@([0-9]+) .*$')
# Regular expression that matches a single commit footer line.
COMMIT_FOOTER_ENTRY_RE = re.compile(r'([^:]+):\s*(.*)')
# Footer metadata key for commit position.
COMMIT_POSITION_FOOTER_KEY = 'Cr-Commit-Position'
# Regular expression to parse a commit position
COMMIT_POSITION_RE = re.compile(r'(.+)@\{#(\d+)\}')
# Key for the 'git-svn' ID metadata commit footer entry.
GIT_SVN_ID_FOOTER_KEY = 'git-svn-id'
# e.g., git-svn-id: https://v8.googlecode.com/svn/trunk@23117
# ce2b1a6d-e550-0410-aec6-3dcde31c8c00
GIT_SVN_ID_RE = re.compile(r'[^@]+@(\d+)\s+(?:[a-zA-Z0-9\-]+)')
# Copied from bot_update.py.
def GetCommitMessageFooterMap(message):
"""Returns: (dict) A dictionary of commit message footer entries.
"""
footers = {}
# Extract the lines in the footer block.
lines = []
for line in message.strip().splitlines():
line = line.strip()
if len(line) == 0:
del(lines[:])
continue
lines.append(line)
# Parse the footer
for line in lines:
m = COMMIT_FOOTER_ENTRY_RE.match(line)
if not m:
# If any single line isn't valid, continue anyway for compatibility with
# Gerrit (which itself uses JGit for this).
continue
footers[m.group(1)] = m.group(2).strip()
return footers
class GitFailedException(Exception):
pass
def Strip(f):
def new_f(*args, **kwargs):
result = f(*args, **kwargs)
if result is None:
return result
else:
return result.strip()
return new_f
def MakeArgs(l):
"""['-a', '', 'abc', ''] -> '-a abc'"""
return " ".join(filter(None, l))
def Quoted(s):
return "\"%s\"" % s
class GitRecipesMixin(object):
def GitIsWorkdirClean(self, **kwargs):
return self.Git("status -s -uno", **kwargs).strip() == ""
@Strip
def GitBranch(self, **kwargs):
return self.Git("branch", **kwargs)
def GitCreateBranch(self, name, remote="", **kwargs):
assert name
remote_args = ["--upstream", remote] if remote else []
self.Git(MakeArgs(["new-branch", name] + remote_args), **kwargs)
def GitDeleteBranch(self, name, **kwargs):
assert name
self.Git(MakeArgs(["branch -D", name]), **kwargs)
def GitReset(self, name, **kwargs):
assert name
self.Git(MakeArgs(["reset --hard", name]), **kwargs)
def GitStash(self, **kwargs):
self.Git(MakeArgs(["stash"]), **kwargs)
def GitRemotes(self, **kwargs):
return map(str.strip,
self.Git(MakeArgs(["branch -r"]), **kwargs).splitlines())
def GitCheckout(self, name, **kwargs):
assert name
self.Git(MakeArgs(["checkout -f", name]), **kwargs)
def GitCheckoutFile(self, name, branch_or_hash, **kwargs):
assert name
assert branch_or_hash
self.Git(MakeArgs(["checkout -f", branch_or_hash, "--", name]), **kwargs)
def GitCheckoutFileSafe(self, name, branch_or_hash, **kwargs):
try:
self.GitCheckoutFile(name, branch_or_hash, **kwargs)
except GitFailedException: # pragma: no cover
# The file doesn't exist in that revision.
return False
return True
def GitChangedFiles(self, git_hash, **kwargs):
assert git_hash
try:
files = self.Git(MakeArgs(["diff --name-only",
git_hash,
"%s^" % git_hash]), **kwargs)
return map(str.strip, files.splitlines())
except GitFailedException: # pragma: no cover
# Git fails using "^" at branch roots.
return []
@Strip
def GitCurrentBranch(self, **kwargs):
for line in self.Git("status -s -b -uno", **kwargs).strip().splitlines():
match = re.match(r"^## (.+)", line)
if match: return match.group(1)
raise Exception("Couldn't find curent branch.") # pragma: no cover
@Strip
def GitLog(self, n=0, format="", grep="", git_hash="", parent_hash="",
branch="", path=None, reverse=False, **kwargs):
assert not (git_hash and parent_hash)
args = ["log"]
if n > 0:
args.append("-%d" % n)
if format:
args.append("--format=%s" % format)
if grep:
args.append("--grep=\"%s\"" % grep.replace("\"", "\\\""))
if reverse:
args.append("--reverse")
if git_hash:
args.append(git_hash)
if parent_hash:
args.append("%s^" % parent_hash)
args.append(branch)
if path:
args.extend(["--", path])
return self.Git(MakeArgs(args), **kwargs)
def GitShowFile(self, refspec, path, **kwargs):
assert refspec
assert path
return self.Git(MakeArgs(["show", "%s:%s" % (refspec, path)]), **kwargs)
def GitGetPatch(self, git_hash, **kwargs):
assert git_hash
return self.Git(MakeArgs(["log", "-1", "-p", git_hash]), **kwargs)
# TODO(machenbach): Unused? Remove.
def GitAdd(self, name, **kwargs):
assert name
self.Git(MakeArgs(["add", Quoted(name)]), **kwargs)
def GitApplyPatch(self, patch_file, reverse=False, **kwargs):
assert patch_file
args = ["apply --index --reject"]
if reverse:
args.append("--reverse")
args.append(Quoted(patch_file))
self.Git(MakeArgs(args), **kwargs)
def GitUpload(self, reviewer="", force=False, cq=False,
cq_dry_run=False, set_bot_commit=False, bypass_hooks=False,
cc="", tbr_reviewer="", no_autocc=False, message_file=None,
**kwargs):
args = ["cl upload --send-mail"]
if reviewer:
args += ["-r", Quoted(reviewer)]
if tbr_reviewer:
args += ["--tbrs", Quoted(tbr_reviewer)]
if force:
args.append("-f")
if cq:
args.append("--use-commit-queue")
if cq_dry_run:
args.append("--cq-dry-run")
if set_bot_commit:
args.append("--set-bot-commit")
if bypass_hooks:
args.append("--bypass-hooks")
if no_autocc:
args.append("--no-autocc")
if cc:
args += ["--cc", Quoted(cc)]
if message_file:
args += ["--message-file", Quoted(message_file)]
# TODO(machenbach): Check output in forced mode. Verify that all required
# base files were uploaded, if not retry.
self.Git(MakeArgs(args), pipe=False, **kwargs)
def GitCommit(self, message="", file_name="", author=None, prefix=None, **kwargs):
assert message or file_name
args = (prefix or []) + ["commit"]
if file_name:
args += ["-aF", Quoted(file_name)]
if message:
args += ["-m", Quoted(message)]
if author:
args += ["--author", "\"%s <%s>\"" % (author, author)]
self.Git(MakeArgs(args), **kwargs)
def GitPresubmit(self, **kwargs):
self.Git("cl presubmit", "PRESUBMIT_TREE_CHECK=\"skip\"", **kwargs)
def GitCLLand(self, **kwargs):
self.Git(
"cl land -f --bypass-hooks", retry_on=lambda x: x is None, **kwargs)
def GitDiff(self, loc1, loc2, **kwargs):
return self.Git(MakeArgs(["diff", loc1, loc2]), **kwargs)
def GitPull(self, **kwargs):
self.Git("pull", **kwargs)
def GitFetchOrigin(self, *refspecs, **kwargs):
self.Git(MakeArgs(["fetch", "origin"] + list(refspecs)), **kwargs)
@Strip
# Copied from bot_update.py and modified for svn-like numbers only.
def GetCommitPositionNumber(self, git_hash, **kwargs):
"""Dumps the 'git' log for a specific revision and parses out the commit
position number.
If a commit position metadata key is found, its number will be returned.
Otherwise, we will search for a 'git-svn' metadata entry. If one is found,
its SVN revision value is returned.
"""
git_log = self.GitLog(format='%B', n=1, git_hash=git_hash, **kwargs)
footer_map = GetCommitMessageFooterMap(git_log)
# Search for commit position metadata
value = footer_map.get(COMMIT_POSITION_FOOTER_KEY)
if value:
match = COMMIT_POSITION_RE.match(value)
if match:
return match.group(2)
# Extract the svn revision from 'git-svn' metadata
value = footer_map.get(GIT_SVN_ID_FOOTER_KEY)
if value:
match = GIT_SVN_ID_RE.match(value)
if match:
return match.group(1)
raise GitFailedException("Couldn't determine commit position for %s" %
git_hash)
def GitGetHashOfTag(self, tag_name, **kwargs):
return self.Git("rev-list -1 " + tag_name).strip().encode("ascii", "ignore")

192
deps/v8/tools/release/list_deprecated.py vendored Executable file
View File

@ -0,0 +1,192 @@
#!/usr/bin/env python3
# Copyright 2018 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.
import argparse
from datetime import datetime
import re
import subprocess
import sys
from pathlib import Path
import logging
from multiprocessing import Pool
RE_GITHASH = re.compile(r"^[0-9a-f]{40}")
RE_AUTHOR_TIME = re.compile(r"^author-time (\d+)$")
RE_FILENAME = re.compile(r"^filename (.+)$")
VERSION_CACHE = dict()
RE_VERSION_MAJOR = re.compile(r".*V8_MAJOR_VERSION ([0-9]+)")
RE_VERSION_MINOR = re.compile(r".*V8_MINOR_VERSION ([0-9]+)")
RE_MACRO_END = re.compile(r"\);")
RE_DEPRECATE_MACRO = re.compile(r"\(.*?,(.*)\);", re.MULTILINE)
class HeaderFile(object):
def __init__(self, path):
self.path = path
self.blame_list = self.get_blame_list()
@classmethod
def get_api_header_files(cls, options):
files = subprocess.check_output(
['git', 'ls-tree', '--name-only', '-r', 'HEAD', options.include_dir],
encoding='UTF-8')
files = map(Path, filter(lambda l: l.endswith('.h'), files.splitlines()))
with Pool(processes=24) as pool:
return pool.map(cls, files)
def extract_version(self, hash):
if hash in VERSION_CACHE:
return VERSION_CACHE[hash]
if hash == '0000000000000000000000000000000000000000':
return 'HEAD'
result = subprocess.check_output(
['git', 'show', f"{hash}:include/v8-version.h"], encoding='UTF-8')
major = RE_VERSION_MAJOR.search(result).group(1)
minor = RE_VERSION_MINOR.search(result).group(1)
version = f"{major}.{minor}"
VERSION_CACHE[hash] = version
return version
def get_blame_list(self):
logging.info(f"blame list for {self.path}")
result = subprocess.check_output(
['git', 'blame', '-t', '--line-porcelain', self.path],
encoding='UTF-8')
line_iter = iter(result.splitlines())
blame_list = list()
current_blame = None
while True:
line = next(line_iter, None)
if line is None:
break
if RE_GITHASH.match(line):
if current_blame is not None:
blame_list.append(current_blame)
hash = line.split(" ")[0]
current_blame = {
'datetime': 0,
'filename': None,
'content': None,
'hash': hash
}
continue
match = RE_AUTHOR_TIME.match(line)
if match:
current_blame['datetime'] = datetime.fromtimestamp(
int(match.groups()[0]))
continue
match = RE_FILENAME.match(line)
if match:
current_blame['filename'] = match.groups()[0]
current_blame['content'] = next(line_iter).strip()
continue
blame_list.append(current_blame)
return blame_list
def filter_and_print(self, macro, options):
before = options.before
index = 0
re_macro = re.compile(macro)
deprecated = list()
while index < len(self.blame_list):
blame = self.blame_list[index]
line = blame['content']
if line.startswith("#") or line.startswith("//"):
index += 1
continue
commit_datetime = blame['datetime']
if commit_datetime >= before:
index += 1
continue
commit_hash = blame['hash']
match = re_macro.search(line)
if match:
pos = match.end()
start = -1
parens = 0
while True:
if pos >= len(line):
# Extend to next line
index = index + 1
blame = self.blame_list[index]
line = line + blame['content']
if line[pos] == '(':
parens = parens + 1
elif line[pos] == ')':
parens = parens - 1
if parens == 0:
# Exclude closing ")
pos = pos - 1
break
elif line[pos] == '"' and start == -1:
start = pos + 1
pos = pos + 1
# Extract content and replace double quotes from merged lines
content = line[start:pos].strip().replace('""', '')
deprecated.append((index + 1, commit_datetime, commit_hash, content))
index = index + 1
for linenumber, commit_datetime, commit_hash, content in deprecated:
self.print_details(linenumber, commit_datetime, commit_hash, content)
def print_details(self, linenumber, commit_datetime, commit_hash, content):
commit_date = commit_datetime.date()
file_position = (f"{self.path}:{linenumber}").ljust(40)
v8_version = f"v{self.extract_version(commit_hash)}".rjust(5)
print(f"{file_position} {v8_version} {commit_date} {commit_hash[:8]}"
f" {content}")
def print_v8_version(self, options):
commit_hash, commit_datetime = subprocess.check_output(
['git', 'log', '-1', '--format=%H%n%ct', self.path],
encoding='UTF-8').splitlines()
commit_datetime = datetime.fromtimestamp(int(commit_datetime))
self.print_details(11, commit_datetime, commit_hash, content="")
def parse_options(args):
parser = argparse.ArgumentParser(
description="Collect deprecation statistics")
parser.add_argument("include_dir", nargs='?', help="Path to includes dir")
parser.add_argument("--before", help="Filter by date")
parser.add_argument("--verbose",
"-v",
help="Verbose logging",
action="store_true")
options = parser.parse_args(args)
if options.verbose:
logging.basicConfig(level=logging.DEBUG)
if options.before:
options.before = datetime.strptime(options.before, '%Y-%m-%d')
else:
options.before = datetime.now()
if options.include_dir is None:
base_path = Path(__file__).parent.parent
options.include_dir = str((base_path / 'include').relative_to(base_path))
return options
def main(args):
options = parse_options(args)
print("# CURRENT V8 VERSION:")
version = HeaderFile(Path(options.include_dir) / 'v8-version.h')
version.print_v8_version(options)
header_files = HeaderFile.get_api_header_files(options)
print("\n")
print("# V8_DEPRECATE_SOON:")
for header in header_files:
header.filter_and_print("V8_DEPRECATE_SOON", options)
print("\n")
print("# V8_DEPRECATED:")
for header in header_files:
header.filter_and_print("V8_DEPRECATED", options)
if __name__ == "__main__":
main(sys.argv[1:])

262
deps/v8/tools/release/merge_to_branch.py vendored Executable file
View File

@ -0,0 +1,262 @@
#!/usr/bin/env python3
# Copyright 2014 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from collections import OrderedDict
import sys
from common_includes import *
from git_recipes import GetCommitMessageFooterMap
def IsSvnNumber(rev):
return rev.isdigit() and len(rev) < 8
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
if os.path.exists(self.Config("ALREADY_MERGING_SENTINEL_FILE")):
if self._options.force:
os.remove(self.Config("ALREADY_MERGING_SENTINEL_FILE"))
elif self._options.step == 0: # pragma: no cover
self.Die("A merge is already in progress. Use -f to continue")
open(self.Config("ALREADY_MERGING_SENTINEL_FILE"), "a").close()
self.InitialEnvironmentChecks(self.default_cwd)
self["merge_to_branch"] = self._options.branch
self.CommonPrepare()
self.PrepareBranch()
class CreateBranch(Step):
MESSAGE = "Create a fresh branch for the patch."
def RunStep(self):
self.GitCreateBranch(self.Config("BRANCHNAME"),
self.vc.RemoteBranch(self["merge_to_branch"]))
class SearchArchitecturePorts(Step):
MESSAGE = "Search for corresponding architecture ports."
def RunStep(self):
self["full_revision_list"] = list(OrderedDict.fromkeys(
self._options.revisions))
port_revision_list = []
for revision in self["full_revision_list"]:
# Search for commits which matches the "Port XXX" pattern.
git_hashes = self.GitLog(reverse=True, format="%H",
grep="^[Pp]ort %s" % revision,
branch=self.vc.RemoteMainBranch())
for git_hash in git_hashes.splitlines():
revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash)
# Is this revision included in the original revision list?
if git_hash in self["full_revision_list"]:
print("Found port of %s -> %s (already included): %s"
% (revision, git_hash, revision_title))
else:
print("Found port of %s -> %s: %s"
% (revision, git_hash, revision_title))
port_revision_list.append(git_hash)
# Do we find any port?
if len(port_revision_list) > 0:
if self.Confirm("Automatically add corresponding ports (%s)?"
% ", ".join(port_revision_list)):
#: 'y': Add ports to revision list.
self["full_revision_list"].extend(port_revision_list)
class CreateCommitMessage(Step):
MESSAGE = "Create commit message."
def _create_commit_description(self, commit_hash):
patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash)
description = "Merged: " + patch_merge_desc + "\n"
description += "Revision: " + commit_hash + "\n\n"
return description
def RunStep(self):
# Stringify: ["abcde", "12345"] -> "abcde, 12345"
self["revision_list"] = ", ".join(self["full_revision_list"])
if not self["revision_list"]: # pragma: no cover
self.Die("Revision list is empty.")
msg_pieces = []
if len(self["full_revision_list"]) > 1:
self["commit_title"] = "Merged: Squashed multiple commits."
for commit_hash in self["full_revision_list"]:
msg_pieces.append(self._create_commit_description(commit_hash))
else:
commit_hash = self["full_revision_list"][0]
full_description = self._create_commit_description(commit_hash).split("\n")
#Truncate title because of code review tool
title = full_description[0]
if len(title) > 100:
title = title[:96] + " ..."
self["commit_title"] = title
msg_pieces.append(full_description[1] + "\n\n")
bugs = []
for commit_hash in self["full_revision_list"]:
msg = self.GitLog(n=1, git_hash=commit_hash)
for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M):
bugs.extend(s.strip() for s in bug.split(","))
gerrit_bug = GetCommitMessageFooterMap(msg).get('Bug', '')
bugs.extend(s.strip() for s in gerrit_bug.split(","))
bug_aggregate = ",".join(
sorted(filter(lambda s: s and s != "none", set(bugs))))
if bug_aggregate:
# TODO(machenbach): Use proper gerrit footer for bug after switch to
# gerrit. Keep BUG= for now for backwards-compatibility.
msg_pieces.append("BUG=%s\n" % bug_aggregate)
self["new_commit_msg"] = "".join(msg_pieces)
class ApplyPatches(Step):
MESSAGE = "Apply patches for selected revisions."
def RunStep(self):
for commit_hash in self["full_revision_list"]:
print("Applying patch for %s to %s..."
% (commit_hash, self["merge_to_branch"]))
patch = self.GitGetPatch(commit_hash)
TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE"))
self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE"))
if self._options.patch:
self.ApplyPatch(self._options.patch)
class CommitLocal(Step):
MESSAGE = "Commit to local branch."
def RunStep(self):
# Add a commit message title.
self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"],
self["new_commit_msg"])
TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE"))
self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))
class CommitRepository(Step):
MESSAGE = "Commit to the repository."
def RunStep(self):
self.GitCheckout(self.Config("BRANCHNAME"))
self.WaitForLGTM()
self.GitPresubmit()
self.vc.CLLand()
class CleanUp(Step):
MESSAGE = "Cleanup."
def RunStep(self):
self.CommonCleanup()
print("*** SUMMARY ***")
print("branch: %s" % self["merge_to_branch"])
if self["revision_list"]:
print("patches: %s" % self["revision_list"])
class MergeToBranch(ScriptsBase):
def _Description(self):
return ("Performs the necessary steps to merge revisions from "
"main to release branches like 4.5. This script does not "
"version the commit. See http://goo.gl/9ke2Vw for more "
"information.")
def _PrepareOptions(self, parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--branch", help="The branch to merge to.")
parser.add_argument("revisions", nargs="*",
help="The revisions to merge.")
parser.add_argument("-f", "--force",
help="Delete sentinel file.",
default=False, action="store_true")
parser.add_argument("-m", "--message",
help="A commit message for the patch.")
parser.add_argument("-p", "--patch",
help="A patch file to apply as part of the merge.")
def _ProcessOptions(self, options):
if len(options.revisions) < 1:
if not options.patch:
print("Either a patch file or revision numbers must be specified")
return False
if not options.message:
print("You must specify a merge comment if no patches are specified")
return False
options.bypass_upload_hooks = True
if len(options.branch.split('.')) > 2:
print ("This script does not support merging to roll branches. "
"Please use tools/release/roll_merge.py for this use case.")
return False
# Make sure to use git hashes in the new workflows.
for revision in options.revisions:
if (IsSvnNumber(revision) or
(revision[0:1] == "r" and IsSvnNumber(revision[1:]))):
print("Please provide full git hashes of the patches to merge.")
print("Got: %s" % revision)
return False
return True
def _Config(self):
return {
"BRANCHNAME": "prepare-merge",
"PERSISTFILE_BASENAME": RELEASE_WORKDIR + "v8-merge-to-branch-tempfile",
"ALREADY_MERGING_SENTINEL_FILE":
RELEASE_WORKDIR + "v8-merge-to-branch-tempfile-already-merging",
"TEMPORARY_PATCH_FILE":
RELEASE_WORKDIR + "v8-prepare-merge-tempfile-temporary-patch",
"COMMITMSG_FILE": RELEASE_WORKDIR + "v8-prepare-merge-tempfile-commitmsg",
}
def _Steps(self):
return [
Preparation,
CreateBranch,
SearchArchitecturePorts,
CreateCommitMessage,
ApplyPatches,
CommitLocal,
UploadStep,
CommitRepository,
CleanUp,
]
if __name__ == "__main__": # pragma: no cover
sys.exit(MergeToBranch().Run())

View File

@ -0,0 +1,159 @@
#!/usr/bin/env vpython3
# Copyright 2024 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.
import argparse
import logging
import re
import sys
from pathlib import Path
# Add depot tools to the sys path, for gerrit_util
BASE_PATH = Path(__file__).resolve().parent.parent.parent
DEPOT_TOOLS_PATH = BASE_PATH / 'third_party' / 'depot_tools'
sys.path.append(str(DEPOT_TOOLS_PATH))
import gerrit_util
GERRIT_HOST = 'chromium-review.googlesource.com'
TAGS_TO_STRIP = [
"Change-Id",
"Reviewed-on",
"Reviewed-by",
"Commit-Queue",
"Cr-Commit-Position",
"Auto-Submit",
]
# TODO(leszeks): Upstream to gerrit_util
def CherryPickWithMessage(host,
change,
destination,
revision='current',
message=None):
"""Create a cherry-pick commit from the given change, onto the given
destination.
"""
path = 'changes/%s/revisions/%s/cherrypick' % (change, revision)
body = {'destination': destination, 'allow_conflicts': True}
if message is not None:
body['message'] = message
conn = gerrit_util.CreateHttpConn(host, path, reqtype='POST', body=body)
return gerrit_util.ReadHttpJsonResponse(conn)
def main(sys_args=None):
sys_args = sys_args or sys.argv[1:]
parser = argparse.ArgumentParser(
description="Use the gerrit API to cherry-pick a revision onto a branch")
parser.add_argument(
"-r",
"--reviewer",
help="The author email used for code review (defaults to reviewers of the"
"original CL). Can specify multiple reviewers. Appends @chromium.org if "
"not specified.",
default=[],
nargs="*")
parser.add_argument(
"-b",
"--branch",
help="The branch to merge to (e.g. 12.0).",
required=True)
# TODO(leszeks): Add support for more than one revision. This will need to
# cherry pick one of them using the gerrit API, then somehow applying the rest
# onto it as additional patches.
parser.add_argument("revision", nargs=1, help="The revision to merge.")
options = parser.parse_args(sys_args)
revision = options.revision[0]
branch = options.branch
if not re.match(r"[0-9]+\.[0-9]+", branch):
print("Branch is not of the form 1.2")
return 1
branch_rev = f'refs/branch-heads/{branch}'
reviewers = []
for reviewer in options.reviewer:
if not re.match(r".+@.+\..+", reviewer):
reviewer = f"{reviewer}@chromium.org"
reviewers.append(reviewer)
print(f"Cherry-picking {revision} onto {branch_rev}")
# Create the commit message, using the new version and information about the
# original commit.
print("Creating commit message...")
original_commit = gerrit_util.GetChangeCommit(GERRIT_HOST, revision)
commit_msg = original_commit['message'].splitlines()
if not reviewers:
for line in commit_msg:
m = re.match(r"^Reviewed-by:\s*(.+)$", line)
if m:
reviewers.append(m.group(1))
print(f"Picking reviewers from original commit: {','.join(reviewers)}")
commit_msg[0] = f'Merged: {commit_msg[0]}'
commit_msg[1:] = [
line for line in commit_msg[1:]
if not re.match(r"^(" + "|".join(TAGS_TO_STRIP) + r")\s*:", line)
]
commit_msg.append(f"(cherry picked from commit {original_commit['commit']})")
print("New commit message:")
for line in commit_msg:
print("| " + line)
# Create a cherry pick commit from the original commit.
cherry_pick = CherryPickWithMessage(
GERRIT_HOST, revision, branch_rev, message="\n".join(commit_msg))
print(cherry_pick)
# Use the cherry pick number to refer to it, rather than the 'id', because
# cherry picks end up having the same Change-Id as the original CL.
cherry_pick_id = cherry_pick['_number']
print(
f"Created cherry-pick: https://{GERRIT_HOST}/c/{cherry_pick['_number']}")
has_conflicts = cherry_pick.get('contains_git_conflicts', False)
if has_conflicts:
print("==================================================")
print("WARNING: Cherry-pick created with merge conflicts.")
print("Resolve locally with:")
print(
f" git cl patch --branch merge-{cherry_pick_id} --force {cherry_pick_id}"
)
print(
f" git fetch --refmap='' origin refs/branch-heads/{branch}:refs/remotes/branch-heads/{branch}"
)
print(f" git branch --set-upstream-to branch-heads/{branch}")
print("==================================================")
# Set Auto-Submit +1
print("Setting 'Auto-Submit +1'...")
try:
gerrit_util.SetReview(
GERRIT_HOST,
cherry_pick_id,
labels={"Auto-Submit": 1},
ready=not has_conflicts)
except:
logging.warning("Could not set Auto-Submit +1")
print(f"Adding {','.join(reviewers)} as reviewer(s)...")
gerrit_util.AddReviewers(
GERRIT_HOST,
cherry_pick_id,
reviewers=reviewers,
notify=not has_conflicts)
print("Done.")
if __name__ == "__main__": # pragma: no cover
sys.exit(main())

140
deps/v8/tools/release/mergeinfo.py vendored Executable file
View File

@ -0,0 +1,140 @@
#!/usr/bin/env python3
# Copyright 2015 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.
# for py2/py3 compatibility
from __future__ import print_function
import argparse
import sys
import re
from subprocess import Popen, PIPE
GIT_OPTION_HASH_ONLY = '--pretty=format:%H'
GIT_OPTION_NO_DIFF = '--quiet'
GIT_OPTION_ONELINE = '--oneline'
def git_execute(working_dir, args, verbose=False):
command = ["git", "-C", working_dir] + args
if verbose:
print("Git working dir: " + working_dir)
print("Executing git command:" + str(command))
p = Popen(args=command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
if p.returncode != 0:
raise Exception(err)
output = output.decode('utf-8')
if verbose:
print("Git return value: " + output)
return output
def describe_commit(git_working_dir, hash_to_search, one_line=False):
if one_line:
return git_execute(git_working_dir, ['show',
GIT_OPTION_NO_DIFF,
GIT_OPTION_ONELINE,
hash_to_search]).strip()
return git_execute(git_working_dir, ['show',
GIT_OPTION_NO_DIFF,
hash_to_search]).strip()
def get_followup_commits(git_working_dir, hash_to_search):
cmd = ['log', '--grep=' + hash_to_search, GIT_OPTION_HASH_ONLY,
'remotes/origin/main'];
return git_execute(git_working_dir, cmd).strip().splitlines()
def get_merge_commits(git_working_dir, hash_to_search):
merges = get_related_commits_not_on_main(git_working_dir, hash_to_search)
false_merges = get_related_commits_not_on_main(
git_working_dir, 'Cr-Branched-From: ' + hash_to_search)
false_merges = set(false_merges)
return ([merge_commit for merge_commit in merges
if merge_commit not in false_merges])
def get_related_commits_not_on_main(git_working_dir, grep_command):
commits = git_execute(git_working_dir, ['log',
'--all',
'--grep=' + grep_command,
GIT_OPTION_ONELINE,
'--decorate',
'--not',
'remotes/origin/main',
GIT_OPTION_HASH_ONLY])
return commits.splitlines()
def get_branches_for_commit(git_working_dir, hash_to_search):
branches = git_execute(git_working_dir, ['branch',
'--contains',
hash_to_search,
'-a']).strip()
branches = branches.splitlines()
return {branch.strip() for branch in branches}
def is_lkgr(branches):
return 'remotes/origin/lkgr' in branches
def get_first_canary(branches):
canaries = ([currentBranch for currentBranch in branches if
currentBranch.startswith('remotes/origin/chromium/')])
canaries.sort()
if len(canaries) == 0:
return 'No Canary coverage'
return canaries[0].split('/')[-1]
def get_first_v8_version(branches):
version_re = re.compile("remotes/origin/[0-9]+\.[0-9]+\.[0-9]+")
versions = [branch for branch in branches if version_re.match(branch)]
if len(versions) == 0:
return "--"
version = versions[0].split("/")[-1]
return version
def print_analysis(git_working_dir, hash_to_search):
print('1.) Searching for "' + hash_to_search + '"')
print('=====================ORIGINAL COMMIT START===================')
print(describe_commit(git_working_dir, hash_to_search))
print('=====================ORIGINAL COMMIT END=====================')
print('2.) General information:')
branches = get_branches_for_commit(git_working_dir, hash_to_search)
print('Is LKGR: ' + str(is_lkgr(branches)))
print('Is on Canary: ' + str(get_first_canary(branches)))
print('First V8 branch: ' + str(get_first_v8_version(branches)) + \
' (Might not be the rolled version)')
print('3.) Found follow-up commits, reverts and ports:')
followups = get_followup_commits(git_working_dir, hash_to_search)
for followup in followups:
print(describe_commit(git_working_dir, followup, True))
print('4.) Found merges:')
merges = get_merge_commits(git_working_dir, hash_to_search)
for currentMerge in merges:
print(describe_commit(git_working_dir, currentMerge, True))
print('---Merged to:')
mergeOutput = git_execute(git_working_dir, ['branch',
'--contains',
currentMerge,
'-r']).strip()
print(mergeOutput)
print('Finished successfully')
if __name__ == '__main__': # pragma: no cover
parser = argparse.ArgumentParser('Tool to check where a git commit was'
' merged and reverted.')
parser.add_argument('-g', '--git-dir', required=False, default='.',
help='The path to your git working directory.')
parser.add_argument('hash',
nargs=1,
help='Hash of the commit to be searched.')
args = sys.argv[1:]
options = parser.parse_args(args)
sys.exit(print_analysis(options.git_dir, options.hash[0]))

457
deps/v8/tools/release/roll_bisect.py vendored Executable file
View File

@ -0,0 +1,457 @@
#!/usr/bin/env python3
# Copyright 2022 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.
import argparse
import logging
import os
import re
import sys
import time
import datetime
import httplib2
import json
from io import StringIO
import urllib.parse
# Add depot tools to the sys path, for gerrit_util
sys.path.append(
os.path.abspath(
os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'../../third_party/depot_tools')))
import gerrit_util
import auth
from common_includes import VERSION_FILE
GERRIT_HOST = 'chromium-review.googlesource.com'
ROLLER_BOT_EMAIL = "chromium-autoroll@skia-public.iam.gserviceaccount.com"
def ExtractVersion(include_file_text):
version = {}
for line in include_file_text.split('\n'):
def ReadAndPersist(var_name, def_name):
match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
if match:
value = match.group(1)
version[var_name] = int(value)
for (var_name, def_name) in [("major", "V8_MAJOR_VERSION"),
("minor", "V8_MINOR_VERSION"),
("build", "V8_BUILD_NUMBER"),
("patch", "V8_PATCH_LEVEL")]:
ReadAndPersist(var_name, def_name)
return version
class HttpError(Exception):
"""Exception class for errors commuicating with a http service."""
def __init__(self, http_status, message, *args, **kwargs):
super(HttpError, self).__init__(*args, **kwargs)
self.http_status = http_status
self.message = '(%d) %s' % (self.http_status, message)
def __str__(self):
return self.message
def HttpQuery(uri, *, timeout=300, **params):
conn = httplib2.Http(timeout=timeout)
response, contents = conn.request(uri=uri, **params)
contents = contents.decode("utf-8", "replace")
return StringIO(contents)
def HttpJSONQuery(*args, **params):
headers = params.setdefault("headers", {})
headers.setdefault("Accept", "application/json")
if "body" in params:
assert (params.get("method", "GET") != "GET")
if not isinstance(params["body"], str):
params["body"] = json.dumps(params["body"])
headers.setdefault("Content-Type", "application/json")
fh = HttpQuery(*args, **params)
s = fh.readline()
if s and s.rstrip() != ")]}'":
raise HttpError(200, "Unexpected json output: %s" % s)
s = fh.read()
if not s:
return None
return json.loads(s)
def QueryTryBotsForFailures(change, patchset, timeout=300):
builds = HttpJSONQuery(
"https://cr-buildbucket.appspot.com/prpc/buildbucket.v2.Builds/SearchBuilds",
method="POST",
body={
"fields":
"builds.*.builder,builds.*.id,builds.*.status,builds.*.startTime,builds.*.endTime",
"predicate": {
"tags": [{
"key": "cq_experimental",
"value": "false"
}],
"status":
"FAILURE",
"gerritChanges": [{
"project": "v8%2Fv8",
"host": "chromium-review.googlesource.com",
"patchset": patchset,
"change": change,
},],
},
},
timeout=timeout)
return builds["builds"]
def main():
parser = argparse.ArgumentParser(
description="Bisect a roll CL using a CQ bot")
parser.add_argument(
"roll",
nargs='?',
help="The roll CL (default: find the most recent active roll)",
default=None)
parser.add_argument(
"bot",
nargs='?',
help="The bot to test, e.g. chromium/try/chromeos-amd64-generic-rel (default: find the most-often crashing, shortest running, existing failing bot on the given roll CL)",
default=None)
parser.add_argument(
"-s",
"--start",
default=None,
help="The start V8 git commit for the bissect (the last known good commit). This is assumed to be good and is not tested. Defaults to the V8 commit as it was before the roll."
)
parser.add_argument(
"-e",
"--end",
default=None,
help="The end V8 git commit for the bisect (the first known bad commit). This is assumed to be bad and is not tested. Defaults to the V8 commit the roll is rolling to."
)
options = parser.parse_args()
roll = options.roll
if roll is None:
print("Looking for latest roll CL...")
changes = gerrit_util.QueryChanges(
GERRIT_HOST, [("owner", ROLLER_BOT_EMAIL), ("project", "chromium/src"),
("status", "NEW")],
"Roll V8 from",
limit=1)
if len(changes) < 1:
print("Didn't find a CL that looks like an active roll")
return 1
if len(changes) > 1:
print("Found multiple CLs that look like an active roll:")
for change in changes:
print(" * %s: https://%s/c/%s" %
(change['subject'], GERRIT_HOST, change['_number']))
return 1
roll_change = changes[0]
roll = roll_change["_number"]
roll_change = gerrit_util.GetChangeDetail(GERRIT_HOST, roll,
["CURRENT_REVISION"])
subject = roll_change['subject']
print("Found: %s" % subject)
m = re.match(r"Roll V8 from ([0-9a-f]+) to ([0-9a-f]+)", subject)
if not m:
print("CL subject is not of the form \"Roll V8 from 123abc to 456def\"")
return 1
roll = roll_change["_number"]
current_revision = roll_change["current_revision"]
patchset = roll_change["revisions"][current_revision]["_number"]
print("Bisecting https://%s/c/%s" % (GERRIT_HOST, roll))
bot = options.bot
if bot is None:
bots = QueryTryBotsForFailures(roll, patchset)
failing_bots = {}
f = "%Y-%m-%dT%H:%M:%S.%fZ"
for bot in bots:
key = "/".join(
bot["builder"][x] for x in ["project", "bucket", "builder"])
print(bot["startTime"], bot["endTime"])
startTime = datetime.datetime.fromisoformat(bot["startTime"])
endTime = datetime.datetime.fromisoformat(bot["endTime"])
duration = endTime - startTime
if key in failing_bots:
failing_bots[key] = (failing_bots[key][0] + 1,
min(failing_bots[key][1], duration))
else:
failing_bots[key] = (1, duration)
print(
"* Failure on %s (https://cr-buildbucket.appspot.com/build/%s) after %s"
% (bot["builder"]["builder"], bot["id"], duration))
if len(failing_bots) == 0:
print("No failing bots found.")
return 1
# Sort descending by count, and ascending by duration:
def sort_key(item):
key, value = item
count, duration = value
return (-count, duration)
failing_bots = sorted(failing_bots.items(), key=sort_key)
print("Failing bots:")
print("\n".join("* %s (%d, %s)" % (bot, count, duration)
for (bot, [count, duration]) in failing_bots))
bot = failing_bots[0][0]
print("Bisecting with bot %s" % bot)
bot_project, bot_bucket, bot_builder = bot.split("/")
if options.start is None or options.end is None:
diff = gerrit_util.CallGerritApi(
GERRIT_HOST,
'changes/%s/revisions/%s/files/DEPS/diff' % (roll, patchset),
reqtype='GET')
for content in diff["content"]:
if "ab" in content:
continue
a = content["a"]
b = content["b"]
if len(a) == 1 and len(b) == 1:
version_before = re.search("'v8_revision': '([0-9a-fA-F]+)'", a[0])
version_after = re.search("'v8_revision': '([0-9a-fA-F]+)'", b[0])
if version_before and version_after:
version_before = version_before.group(1)
version_after = version_after.group(1)
break
print("Found unexpected change:")
print("\n".join("- " + line for line in a))
print("\n".join("+ " + line for line in b))
return 1
else:
print("Didn't find a change in DEPS")
return 1
if options.start is not None:
version_before = options.start
if options.end is not None:
version_after = options.end
print("--")
print("Bisecting range:")
print("%s..%s" % (version_before, version_after))
print("https://chromium.googlesource.com/v8/v8/+log/%s..%s" %
(version_before, version_after))
revision_range_log = HttpJSONQuery(
"https://chromium.googlesource.com/v8/v8/+log/%s..%s?format=JSON" %
(version_before, version_after))["log"]
suspect_shas = []
for revision in revision_range_log:
if revision["author"][
"email"] != "v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com":
suspect_shas.append(revision["commit"])
first_bad = 0
last_good = len(suspect_shas)
def IndexState(i):
if i <= first_bad:
return "Bad"
elif i >= last_good:
return "Good"
else:
return "?"
def SuspectListString():
return "\n".join("* %s (%s)" % (sha, IndexState(i))
for i, sha in enumerate(suspect_shas))
if first_bad < last_good - 1:
suspect_sha = None
print("--")
print("Creating bisect change...")
bisect_change = gerrit_util.CreateChange(
GERRIT_HOST,
roll_change["project"],
subject="[DO NOT SUBMIT] V8 roll bisect")
print("https://%s/c/%s" % (GERRIT_HOST, bisect_change['_number']))
try:
DEPS_file_text = gerrit_util.GetFileContents(GERRIT_HOST,
bisect_change['_number'],
"DEPS").decode('utf-8')
while first_bad < last_good - 1:
print("Suspects:\n%s" % SuspectListString())
# Index 0 is known bad, so search within the remaining indices.
bisect_index = first_bad + (last_good - first_bad) // 2
bisect_sha = suspect_shas[bisect_index]
# Test sha
print("Testing %s" % bisect_sha)
print("Updating v8_revision in DEPS to %s..." % bisect_sha)
DEPS_file_text = re.sub(r"'v8_revision': '[0-9a-fA-F]+'",
r"'v8_revision': '%s'" % bisect_sha,
DEPS_file_text)
print("Editing DEPS file...")
gerrit_util.ChangeEdit(GERRIT_HOST, bisect_change['_number'], "DEPS",
DEPS_file_text)
print("Updating commit message...")
commit_msg = "\n".join([
"[DO NOT SUBMIT] V8 roll bisect", #
"", #
"For '%s': https://%s/c/%s" %
(roll_change['subject'], GERRIT_HOST, roll_change['_number']), #
"", #
"Suspects:", #
SuspectListString(),
"", #
"Change-Id: %s" % bisect_change['change_id'], #
])
gerrit_util.SetChangeEditMessage(GERRIT_HOST, bisect_change['_number'],
commit_msg)
print("Publishing change edit...")
gerrit_util.PublishChangeEdit(GERRIT_HOST, bisect_change['_number'])
bisect_change = gerrit_util.GetChangeDetail(GERRIT_HOST,
bisect_change['_number'],
["CURRENT_REVISION"])
bisect_patchset = bisect_change["revisions"][
bisect_change["current_revision"]]["_number"]
bot_test = HttpJSONQuery(
'https://cr-buildbucket.appspot.com/prpc/buildbucket.v2.Builds/ScheduleBuild',
method="POST",
headers={
'Authorization':
'Bearer %s' % auth.Authenticator().get_access_token().token
},
body={
"builder": {
"project": bot_project,
"bucket": bot_bucket,
"builder": bot_builder
},
"gerritChanges": [{
"host": "chromium-review.googlesource.com",
"project": "chromium/src",
"change": bisect_change["_number"],
"patchset": bisect_patchset
}],
"tags": [{
"key": "builder",
"value": "chromeos-amd64-generic-rel"
}, {
"key": "user_agent",
"value": "leszeks_testing"
}]
})
test_id = bot_test["id"]
waiting_start_time = time.time()
while True:
time.sleep(10)
# Print the waiting time so far
elapsed_time = time.time() - waiting_start_time
print(
"\r - waiting time: %s" %
datetime.timedelta(seconds=round(elapsed_time)),
end="",
flush=True)
test_result_json = HttpJSONQuery(
'https://cr-buildbucket.appspot.com/prpc/buildbucket.v2.Builds/GetBuild',
method="POST",
body={"id": test_id})
if test_result_json["status"] == "STARTED":
continue
elif test_result_json["status"] == "SCHEDULED":
continue
elif test_result_json["status"] == "SUCCESS":
test_result = True
break
elif test_result_json["status"] == "FAILURE":
test_result = False
break
else:
print("Unexpected status: %s" % test_result_json["status"])
return 1
if test_result:
print("%s is good" % bisect_sha)
last_good = bisect_index
else:
print("%s is bad" % bisect_sha)
first_bad = bisect_index
suspect_sha = suspect_shas[first_bad]
print("Updating v8_revision in DEPS to %s..." % suspect_shas[first_bad])
DEPS_file_old = DEPS_file_text
DEPS_file_text = re.sub(r"'v8_revision': '[0-9a-fA-F]+'",
r"'v8_revision': '%s'" % suspect_sha,
DEPS_file_text)
if DEPS_file_old != DEPS_file_text:
print("Editing DEPS file...")
gerrit_util.ChangeEdit(GERRIT_HOST, bisect_change['_number'], "DEPS",
DEPS_file_text)
else:
print("DEPS file left unchanged...")
print("Updating commit message...")
commit_msg = "\n".join([
"[DO NOT SUBMIT] V8 roll bisect (complete)", #
"", #
"For roll %s: https://%s/c/%s" %
(roll_change['subject'], GERRIT_HOST, roll_change['_number']), #
"", #
"Suspects:", #
SuspectListString(),
"", #
"Change-Id: %s" % bisect_change['change_id'], #
])
gerrit_util.SetChangeEditMessage(GERRIT_HOST, bisect_change['_number'],
commit_msg)
print("Publishing change edit...")
gerrit_util.PublishChangeEdit(GERRIT_HOST, bisect_change['_number'])
gerrit_util.AbandonChange(GERRIT_HOST, bisect_change['_number'],
"Complete, suspecting: %s" % suspect_sha)
finally:
if suspect_sha is None:
gerrit_util.AbandonChange(GERRIT_HOST, bisect_change['_number'],
"No suspect found")
else:
assert (first_bad == 0)
suspect_sha = suspect_shas[0]
print("--")
print("Suspecting %s" % suspect_sha)
print("Done.")
if __name__ == "__main__": # pragma: no cover
sys.exit(main())

289
deps/v8/tools/release/roll_merge.py vendored Executable file
View File

@ -0,0 +1,289 @@
#!/usr/bin/env python3
# Copyright 2014 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from collections import OrderedDict
import sys
from common_includes import *
def IsSvnNumber(rev):
return rev.isdigit() and len(rev) < 8
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
if os.path.exists(self.Config("ALREADY_MERGING_SENTINEL_FILE")):
if self._options.force:
os.remove(self.Config("ALREADY_MERGING_SENTINEL_FILE"))
elif self._options.step == 0: # pragma: no cover
self.Die("A merge is already in progress")
open(self.Config("ALREADY_MERGING_SENTINEL_FILE"), "a").close()
self.InitialEnvironmentChecks(self.default_cwd)
if self._options.branch:
self["merge_to_branch"] = self._options.branch
else: # pragma: no cover
self.Die("Please specify a branch to merge to")
self.CommonPrepare()
self.PrepareBranch()
class CreateBranch(Step):
MESSAGE = "Create a fresh branch for the patch."
def RunStep(self):
self.GitCreateBranch(self.Config("BRANCHNAME"),
self.vc.RemoteBranch(self["merge_to_branch"]))
class SearchArchitecturePorts(Step):
MESSAGE = "Search for corresponding architecture ports."
def RunStep(self):
self["full_revision_list"] = list(OrderedDict.fromkeys(
self._options.revisions))
port_revision_list = []
for revision in self["full_revision_list"]:
# Search for commits which matches the "Port XXX" pattern.
git_hashes = self.GitLog(reverse=True, format="%H",
grep="Port %s" % revision,
branch=self.vc.RemoteMainBranch())
for git_hash in git_hashes.splitlines():
revision_title = self.GitLog(n=1, format="%s", git_hash=git_hash)
# Is this revision included in the original revision list?
if git_hash in self["full_revision_list"]:
print("Found port of %s -> %s (already included): %s"
% (revision, git_hash, revision_title))
else:
print("Found port of %s -> %s: %s"
% (revision, git_hash, revision_title))
port_revision_list.append(git_hash)
# Do we find any port?
if len(port_revision_list) > 0:
if self.Confirm("Automatically add corresponding ports (%s)?"
% ", ".join(port_revision_list)):
#: 'y': Add ports to revision list.
self["full_revision_list"].extend(port_revision_list)
class CreateCommitMessage(Step):
MESSAGE = "Create commit message."
def RunStep(self):
# Stringify: ["abcde", "12345"] -> "abcde, 12345"
self["revision_list"] = ", ".join(self["full_revision_list"])
if not self["revision_list"]: # pragma: no cover
self.Die("Revision list is empty.")
action_text = "Merged %s"
# The commit message title is added below after the version is specified.
msg_pieces = [
"\n".join(action_text % s for s in self["full_revision_list"]),
]
msg_pieces.append("\n\n")
for commit_hash in self["full_revision_list"]:
patch_merge_desc = self.GitLog(n=1, format="%s", git_hash=commit_hash)
msg_pieces.append("%s\n\n" % patch_merge_desc)
bugs = []
for commit_hash in self["full_revision_list"]:
msg = self.GitLog(n=1, git_hash=commit_hash)
for bug in re.findall(r"^[ \t]*BUG[ \t]*=[ \t]*(.*?)[ \t]*$", msg, re.M):
bugs.extend(s.strip() for s in bug.split(","))
bug_aggregate = ",".join(sorted(filter(lambda s: s and s != "none", bugs)))
if bug_aggregate:
msg_pieces.append("BUG=%s\n" % bug_aggregate)
self["new_commit_msg"] = "".join(msg_pieces)
class ApplyPatches(Step):
MESSAGE = "Apply patches for selected revisions."
def RunStep(self):
for commit_hash in self["full_revision_list"]:
print("Applying patch for %s to %s..."
% (commit_hash, self["merge_to_branch"]))
patch = self.GitGetPatch(commit_hash)
TextToFile(patch, self.Config("TEMPORARY_PATCH_FILE"))
self.ApplyPatch(self.Config("TEMPORARY_PATCH_FILE"))
if self._options.patch:
self.ApplyPatch(self._options.patch)
class PrepareVersion(Step):
MESSAGE = "Prepare version file."
def RunStep(self):
# This is used to calculate the patch level increment.
self.ReadAndPersistVersion()
class IncrementVersion(Step):
MESSAGE = "Increment version number."
def RunStep(self):
new_patch = str(int(self["patch"]) + 1)
if self.Confirm("Automatically increment V8_PATCH_LEVEL? (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.)" % VERSION_FILE):
text = FileToText(os.path.join(self.default_cwd, VERSION_FILE))
text = MSub(r"(?<=#define V8_PATCH_LEVEL)(?P<space>\s+)\d*$",
r"\g<space>%s" % new_patch,
text)
TextToFile(text, os.path.join(self.default_cwd, VERSION_FILE))
else:
self.Editor(os.path.join(self.default_cwd, VERSION_FILE))
self.ReadAndPersistVersion("new_")
self["version"] = "%s.%s.%s.%s" % (self["new_major"],
self["new_minor"],
self["new_build"],
self["new_patch"])
class CommitLocal(Step):
MESSAGE = "Commit to local branch."
def RunStep(self):
# Add a commit message title.
self["commit_title"] = "Version %s (cherry-pick)" % self["version"]
self["new_commit_msg"] = "%s\n\n%s" % (self["commit_title"],
self["new_commit_msg"])
TextToFile(self["new_commit_msg"], self.Config("COMMITMSG_FILE"))
self.GitCommit(file_name=self.Config("COMMITMSG_FILE"))
class CommitRepository(Step):
MESSAGE = "Commit to the repository."
def RunStep(self):
self.GitCheckout(self.Config("BRANCHNAME"))
self.WaitForLGTM()
self.GitPresubmit()
self.vc.CLLand()
class TagRevision(Step):
MESSAGE = "Create the tag."
def RunStep(self):
print("Creating tag %s" % self["version"])
self.vc.Tag(self["version"],
self.vc.RemoteBranch(self["merge_to_branch"]),
self["commit_title"])
class CleanUp(Step):
MESSAGE = "Cleanup."
def RunStep(self):
self.CommonCleanup()
print("*** SUMMARY ***")
print("version: %s" % self["version"])
print("branch: %s" % self["merge_to_branch"])
if self["revision_list"]:
print("patches: %s" % self["revision_list"])
class RollMerge(ScriptsBase):
def _Description(self):
return ("Performs the necessary steps to merge revisions from "
"main to other branches, including candidates and roll branches.")
def _PrepareOptions(self, parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--branch", help="The branch to merge to.")
parser.add_argument("revisions", nargs="*",
help="The revisions to merge.")
parser.add_argument("-f", "--force",
help="Delete sentinel file.",
default=False, action="store_true")
parser.add_argument("-m", "--message",
help="A commit message for the patch.")
parser.add_argument("-p", "--patch",
help="A patch file to apply as part of the merge.")
def _ProcessOptions(self, options):
if len(options.revisions) < 1:
if not options.patch:
print("Either a patch file or revision numbers must be specified")
return False
if not options.message:
print("You must specify a merge comment if no patches are specified")
return False
options.bypass_upload_hooks = True
# Make sure to use git hashes in the new workflows.
for revision in options.revisions:
if (IsSvnNumber(revision) or
(revision[0:1] == "r" and IsSvnNumber(revision[1:]))):
print("Please provide full git hashes of the patches to merge.")
print("Got: %s" % revision)
return False
return True
def _Config(self):
return {
"BRANCHNAME": "prepare-merge",
"PERSISTFILE_BASENAME":
RELEASE_WORKDIR + "v8-merge-to-branch-tempfile",
"ALREADY_MERGING_SENTINEL_FILE":
RELEASE_WORKDIR + "v8-merge-to-branch-tempfile-already-merging",
"TEMPORARY_PATCH_FILE":
RELEASE_WORKDIR + "v8-prepare-merge-tempfile-temporary-patch",
"COMMITMSG_FILE": RELEASE_WORKDIR + "v8-prepare-merge-tempfile-commitmsg",
}
def _Steps(self):
return [
Preparation,
CreateBranch,
SearchArchitecturePorts,
CreateCommitMessage,
ApplyPatches,
PrepareVersion,
IncrementVersion,
CommitLocal,
UploadStep,
CommitRepository,
TagRevision,
CleanUp,
]
if __name__ == "__main__": # pragma: no cover
sys.exit(RollMerge().Run())

302
deps/v8/tools/release/roll_merge_gerrit.py vendored Executable file
View File

@ -0,0 +1,302 @@
#!/usr/bin/env vpython3
# Copyright 2022 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.
import argparse
import base64
import logging
import re
import sys
import time
import datetime
import urllib.parse
from pathlib import Path
# Add depot tools to the sys path, for gerrit_util
BASE_PATH = Path(__file__).resolve().parent.parent.parent
DEPOT_TOOLS_PATH = BASE_PATH / 'third_party' / 'depot_tools'
sys.path.append(str(DEPOT_TOOLS_PATH))
import gerrit_util
from common_includes import VERSION_FILE
GERRIT_HOST = 'chromium-review.googlesource.com'
ROLLER_BOT_EMAIL = 'chromium-autoroll@skia-public.iam.gserviceaccount.com'
AUTO_ROLLER_URL = 'https://autoroll.skia.org/r/v8-chromium-autoroll'
VERSION_RE = re.compile(r"^\d+(?:\.\d+){2,3}$")
def ExtractVersion(include_file_text):
version = {}
for line in include_file_text.split('\n'):
def ReadAndPersist(var_name, def_name):
match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
if match:
value = match.group(1)
version[var_name] = int(value)
for (var_name, def_name) in [("major", "V8_MAJOR_VERSION"),
("minor", "V8_MINOR_VERSION"),
("build", "V8_BUILD_NUMBER"),
("patch", "V8_PATCH_LEVEL")]:
ReadAndPersist(var_name, def_name)
return version
def repeat_until_true(fun):
waiting_start_time = time.time()
while True:
# Print the waiting time so far
elapsed_time = time.time() - waiting_start_time
elapsed_time = datetime.timedelta(seconds=round(elapsed_time))
print(f"\r - waiting time: {elapsed_time}", end="", flush=True)
if fun():
# New line after progress printing.
print("")
break
time.sleep(5)
def normalize_version(version):
"""Returns an int tuple of the version with exactly 4 positions."""
version = tuple(version.split('.'))
assert 0 < len(version) <= 4
version = tuple(int(c) for c in version)
return (version + (0, ) * 3)[:4]
def main(sys_args=None):
sys_args = sys_args or sys.argv[1:]
parser = argparse.ArgumentParser(
description="Use the gerrit API to cherry-pick a revision")
parser.add_argument(
"-a",
"--author",
default="",
help="The author email used for code review.")
parser.add_argument(
"--branch",
help="The branch to merge to (e.g. 10.3.171). Detected automatically from the latest roll CL if not provided",
required=False)
# TODO(leszeks): Add support for more than one revision. This will need to
# cherry pick one of them using the gerrit API, then somehow applying the rest
# onto it as additional patches.
parser.add_argument("revision", nargs=1, help="The revision to merge.")
options = parser.parse_args(sys_args)
branch = options.branch
if branch is None:
print("Looking for latest roll CL...")
changes = gerrit_util.QueryChanges(
GERRIT_HOST, [
("owner", ROLLER_BOT_EMAIL),
("project", "chromium/src"),
("status", "NEW"),
],
"Roll V8 from",
o_params=['CURRENT_REVISION', 'CURRENT_COMMIT'],
limit=2)
if len(changes) < 1:
print("Didn't find a CL that looks like an active roll")
return 1
if len(changes) > 1:
print("Found multiple CLs that look like an active roll:")
for change in changes:
print(" * %s: https://%s/c/%s" %
(change['subject'], GERRIT_HOST, change['_number']))
return 1
roll_change = changes[0]
subject = roll_change['subject']
message = roll_change['revisions'][
roll_change['current_revision']]['commit']['message']
print("Found roll CL:")
for line in message.splitlines():
print(("> %s" % line).rstrip())
m = re.search(r"Version ([0-9]+\.[0-9]+\.[0-9]+)", message)
if not m:
print("CL message doesn't have a \"Version 1.2.3\" commit listed")
return 1
branch = m.group(1)
# Get the original commit.
revision = options.revision[0]
if not re.match(r"[0-9]+\.[0-9]+\.[0-9]+", branch):
print("Branch is not of the form 1.2.3")
return 1
print("Cherry-picking %s onto %s" % (revision, branch))
# Create a cherry pick commit from the original commit.
cherry_pick = gerrit_util.CherryPick(GERRIT_HOST, revision, branch)
# Use the cherry pick number to refer to it, rather than the 'id', because
# cherry picks end up having the same Change-Id as the original CL.
cherry_pick_id = cherry_pick['_number']
print("Created cherry-pick: https://%s/c/%s" %
(GERRIT_HOST, cherry_pick['_number']))
print("Extracting target version...")
# Get the version out of the cherry-picked commit's v8-version.h
include_file_text = gerrit_util.GetFileContents(GERRIT_HOST, cherry_pick_id,
VERSION_FILE).decode('utf-8')
version = ExtractVersion(include_file_text)
print("... version is %s" % str(version))
new_patch = version['patch'] + 1
version_string = "%d.%d.%d.%d" % (version["major"], version["minor"],
version["build"], new_patch)
# Add the 'roll_merge' hashtag so that Rubberstamper knows that this is a
# benign cherry pick.
print("Setting 'roll-merge' hashtag...")
gerrit_util.CallGerritApi(
GERRIT_HOST,
'changes/%s/hashtags' % cherry_pick_id,
reqtype='POST',
body={'add': ["roll-merge"]})
# Increment the patch number in v8-version.h
print("Updating %s to patch level %d..." % (VERSION_FILE, new_patch))
include_file_text = re.sub(
r"(?<=#define V8_PATCH_LEVEL)(?P<space>\s+)\d*$",
r"\g<space>%d" % new_patch,
include_file_text,
flags=re.MULTILINE)
gerrit_util.ChangeEdit(GERRIT_HOST, cherry_pick_id, VERSION_FILE,
include_file_text)
# Create the commit message, using the new version and information about the
# original commit.
print("Updating commit message...")
original_commit = gerrit_util.GetChangeCommit(GERRIT_HOST, revision)
commit_msg = "\n".join([
"Version %s (cherry-pick)" % version_string, #
"", #
"Merged %s" % original_commit['commit'], #
"", #
"%s" % original_commit['subject'], #
"", #
"Change-Id: %s" % cherry_pick['change_id'], #
])
gerrit_util.SetChangeEditMessage(GERRIT_HOST, cherry_pick_id, commit_msg)
# Publish the change edit with the v8-version.h and commit message changes.
print("Publishing changes...")
gerrit_util.PublishChangeEdit(GERRIT_HOST, cherry_pick_id)
# Set Owners-Override +1
print("Setting 'Owners-Override +1'...")
try:
gerrit_util.SetReview(
GERRIT_HOST, cherry_pick_id, labels={"Owners-Override": 1})
except:
logging.warning("Could not set Owners-Override +1")
print("Adding Rubber Stamper as a reviewer...")
gerrit_util.AddReviewers(
GERRIT_HOST,
cherry_pick_id,
reviewers=['rubber-stamper@appspot.gserviceaccount.com'])
print("Cherry-pick %s created successfully: https://%s/c/%s" %
(version_string, GERRIT_HOST, cherry_pick['_number']))
print("Waiting for Code-Review +1 or Bot-Commit +1...")
while True:
cherry_pick_review = gerrit_util.CallGerritApi(
GERRIT_HOST,
'changes/%s/revisions/current/review' % cherry_pick_id,
reqtype='GET')
if any(
cr_label.get('value', None) == 1
for cr_label in cherry_pick_review['labels']['Code-Review']['all']):
break
if any(
cr_label.get('value', None) == 1
for cr_label in cherry_pick_review['labels']['Bot-Commit']['all']):
break
time.sleep(5)
print("Submitting...")
cherry_pick = gerrit_util.SubmitChange(GERRIT_HOST, cherry_pick_id)
assert cherry_pick['status'] == 'MERGED'
cherry_pick_commit = gerrit_util.GetChangeCommit(GERRIT_HOST, cherry_pick_id,
'current')
print("Found committed as %s..." % cherry_pick_commit['commit'])
print("Setting %s tag..." % version_string)
project = urllib.parse.quote_plus(cherry_pick["project"])
gerrit_util.CreateGerritTag(GERRIT_HOST, project, version_string,
cherry_pick_commit['commit'])
def gerrit_project_get(url):
return gerrit_util.CallGerritApi(
GERRIT_HOST,
f'projects/{project}/{url}',
reqtype='GET',
accept_statuses=[200, 404])
def gerrit_file_content(commit, file_name):
file_name = urllib.parse.quote_plus(file_name)
url = f'projects/{project}/commits/{commit}/files/{file_name}/content'
conn = gerrit_util.CreateHttpConn(GERRIT_HOST, url)
fh = gerrit_util.ReadHttpResponse(conn, accept_statuses=[200, 404])
return base64.b64decode(fh.read()).decode('utf-8')
def is_safe_to_open_roller():
"""Return True if V8's infrastructure detected the patched roll and
reset the roll ref.
"""
ref_info = gerrit_project_get('branches/roll')
assert ref_info['ref'] == 'refs/heads/roll'
revision = ref_info['revision']
roll_version_file = gerrit_file_content(revision, VERSION_FILE)
roll_version = '{major}.{minor}.{build}.{patch}'.format(
**ExtractVersion(roll_version_file))
# The version V8's infra is currently trying to roll.
roll_version = normalize_version(roll_version)
# The new version we want to roll now after the patch.
next_version = normalize_version(version_string)
# A dummy current version, with patch-level - 1.
current_version = next_version[:3] + (next_version[3] - 1,)
# V8's infra resets the roll ref to the last version in Chrome.
# We don't know exactly what it is here, but it must be older than
# the current patched - 1 as we either patch a stuck roll (that
# hasn't landed yet) or a reverted roll.
return roll_version == next_version or roll_version < current_version
print("Waiting until it's safe to reopen rolling, might take 2-3 minutes...")
repeat_until_true(is_safe_to_open_roller)
print(f"It's safe now to reopen the auto roller at {AUTO_ROLLER_URL}.")
def is_pgo_tag_created():
pgo_tag = gerrit_project_get(f'tags/{version_string}-pgo')
if pgo_tag:
assert pgo_tag['revision'] == cherry_pick_commit['commit'], (
f"PGO tagged revision {pgo_tag['revision']} does not match tagged "
f"cherry-pick {cherry_pick_commit['commit']}")
return True
return False
print("Waiting for PGO profile tag (%s-pgo), this can take 15-20 minutes..." %
version_string)
repeat_until_true(is_pgo_tag_created)
print("Done.")
if __name__ == "__main__": # pragma: no cover
sys.exit(main())

View File

@ -0,0 +1,123 @@
#!/usr/bin/env vpython3
# Copyright 2023 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.
import base64
import contextlib
import io
import os
import sys
import unittest
from mock import patch
from pathlib import Path
import roll_merge_gerrit
TEST_DATA = Path(__file__).resolve().parent / 'testdata'
HAPPY_PATH_LOG = TEST_DATA / 'roll_merge_gerrit_happy_path.txt'
ROLL_COMMIT_MESSAGE = """V8 roll from 123abc to 456def
https://chromium.googlesource.com/v8/v8.git/+log/123abc..456def
2038-01-19 v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com Version 1.2.3
"""
V8_VERSION_FILE_AT_CHERRY_PICK = """
#define V8_MAJOR_VERSION 1
#define V8_MINOR_VERSION 2
#define V8_BUILD_NUMBER 3
#define V8_PATCH_LEVEL 0
""".encode('utf-8')
V8_VERSION_FILE_AT_ROLL = """
#define V8_MAJOR_VERSION 1
#define V8_MINOR_VERSION 2
#define V8_BUILD_NUMBER 1
#define V8_PATCH_LEVEL 0
""".encode('utf-8')
V8_VERSION_ROLL_RESPONSE = io.StringIO(
base64.b64encode(V8_VERSION_FILE_AT_ROLL).decode('utf-8'))
class TestStats(unittest.TestCase):
@patch(
'gerrit_util.QueryChanges',
return_value=[{
'subject': ROLL_COMMIT_MESSAGE.splitlines()[0],
'current_revision': '789acf',
'revisions': {
'789acf': {
'commit': {
'message': ROLL_COMMIT_MESSAGE
}
}
}
}])
@patch(
'gerrit_util.CherryPick', return_value={
'_number': 42,
'change_id': 23
})
@patch(
'gerrit_util.GetFileContents',
return_value=V8_VERSION_FILE_AT_CHERRY_PICK)
@patch(
'gerrit_util.CallGerritApi',
side_effect=[
None, {
'labels': {
'Code-Review': {
'all': [{
'value': 1
}]
}
}
}, {
'ref': 'refs/heads/roll',
'revision': 'beefdead'
}, {
'revision': 'deadbeefce'
}
])
@patch('gerrit_util.ChangeEdit')
@patch('gerrit_util.GetChangeCommit', side_effect=[
{'commit': 'deadbeef', 'subject': 'Fix everything'},
{'commit': 'deadbeefce'}])
@patch('gerrit_util.SetChangeEditMessage')
@patch('gerrit_util.PublishChangeEdit')
@patch('gerrit_util.SetReview')
@patch('gerrit_util.AddReviewers')
@patch('gerrit_util.SubmitChange',
return_value={'status': 'MERGED', 'project': 'v8/v8'})
@patch('gerrit_util.CreateGerritTag')
@patch('gerrit_util.CreateHttpConn')
@patch('gerrit_util.ReadHttpResponse',
return_value=V8_VERSION_ROLL_RESPONSE)
def test_happy_path(self, *args):
"""Test the path that succeeds in every step.
The test data above is composed of dummy values, designed
to get the script through from end to end.
"""
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
roll_merge_gerrit.main(['deadbeef'])
actual_stdout = stdout.getvalue().replace('\r', '')
if os.environ.get('GENERATE') == 'true':
with open(HAPPY_PATH_LOG, 'w') as f:
f.write(actual_stdout)
with open(HAPPY_PATH_LOG) as f:
self.assertEqual(
f.read(),
actual_stdout,
'Call testing with the GENERATE=true env var to update the log.')
if __name__ == '__main__':
unittest.main()

53
deps/v8/tools/release/script_test.py vendored Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env python3
# Copyright 2014 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Wraps test execution with a coverage analysis. To get the best speed, the
# native python coverage version >= 3.7.1 should be installed.
import coverage
import os
import unittest
import sys
def Main(argv):
script_path = os.path.dirname(os.path.abspath(__file__))
cov = coverage.coverage(include=([os.path.join(script_path, '*.py')]))
cov.start()
import test_scripts
alltests = map(unittest.TestLoader().loadTestsFromTestCase, [
test_scripts.ToplevelTest,
test_scripts.ScriptTest,
])
unittest.TextTestRunner(verbosity=2).run(unittest.TestSuite(alltests))
cov.stop()
print(cov.report())
if __name__ == '__main__':
sys.exit(Main(sys.argv))

209
deps/v8/tools/release/test_mergeinfo.py vendored Executable file
View File

@ -0,0 +1,209 @@
#!/usr/bin/env python3
# Copyright 2015 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.
import mergeinfo
import shutil
import unittest
from collections import namedtuple
from os import path
from subprocess import Popen, PIPE, check_call
TEST_CONFIG = {
"GIT_REPO": "/tmp/test-v8-search-related-commits",
}
class TestMergeInfo(unittest.TestCase):
base_dir = TEST_CONFIG["GIT_REPO"]
def _execute_git(self, git_args):
fullCommand = ["git", "-C", self.base_dir] + git_args
p = Popen(args=fullCommand, stdin=PIPE,
stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
rc = p.returncode
if rc != 0:
raise Exception(err)
return output
def _update_origin(self):
# Fetch from origin to get/update the origin/main branch
self._execute_git(['fetch', 'origin'])
def setUp(self):
if path.exists(self.base_dir):
shutil.rmtree(self.base_dir)
check_call(["git", "init", self.base_dir])
# Add fake remote with name 'origin'
self._execute_git(['remote', 'add', 'origin', self.base_dir])
# Initial commit
message = '''Initial commit'''
self._make_empty_commit(message)
def tearDown(self):
if path.exists(self.base_dir):
shutil.rmtree(self.base_dir)
def _assert_correct_standard_result(
self, result, all_commits, hash_of_first_commit):
self.assertEqual(len(result), 1, "Main commit not found")
self.assertTrue(
result.get(hash_of_first_commit),
"Main commit is wrong")
self.assertEqual(
len(result[hash_of_first_commit]),
1,
"Child commit not found")
self.assertEqual(
all_commits[2],
result[hash_of_first_commit][0],
"Child commit wrong")
def _get_commits(self):
commits = self._execute_git(
["log", "--format=%H", "--reverse"]).splitlines()
return commits
def _get_branches(self, hash):
return mergeinfo.get_branches_for_commit(self.base_dir, hash)
def _make_empty_commit(self, message):
self._execute_git(["commit", "--allow-empty", "-m", message])
self._update_origin()
return self._get_commits()[-1]
def testCanDescribeCommit(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
result = mergeinfo.describe_commit(
self.base_dir,
hash_of_first_commit).splitlines()
self.assertEqual(
result[0],
'commit ' + hash_of_first_commit)
def testCanDescribeCommitSingleLine(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
result = mergeinfo.describe_commit(
self.base_dir,
hash_of_first_commit, True).splitlines()
self.assertEqual(
str(result[0]),
str(hash_of_first_commit[0:7]) + ' Initial commit')
def testSearchFollowUpCommits(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
message = 'Follow-up commit of ' + hash_of_first_commit
self._make_empty_commit(message)
self._make_empty_commit(message)
self._make_empty_commit(message)
commits = self._get_commits()
message = 'Not related commit'
self._make_empty_commit(message)
followups = mergeinfo.get_followup_commits(
self.base_dir,
hash_of_first_commit)
self.assertEqual(set(followups), set(commits[1:]))
def testSearchMerges(self):
self._execute_git(['branch', 'test'])
self._execute_git(['checkout', 'main'])
message = 'real initial commit'
self._make_empty_commit(message)
commits = self._get_commits()
hash_of_first_commit = commits[0]
self._execute_git(['checkout', 'test'])
message = 'Not related commit'
self._make_empty_commit(message)
# This should be found
message = 'Merge ' + hash_of_first_commit
hash_of_hit = self._make_empty_commit(message)
# This should be ignored
message = 'Cr-Branched-From: ' + hash_of_first_commit
hash_of_ignored = self._make_empty_commit(message)
self._execute_git(['checkout', 'main'])
followups = mergeinfo.get_followup_commits(
self.base_dir,
hash_of_first_commit)
# Check if follow ups and merges are not overlapping
self.assertEqual(len(followups), 0)
message = 'Follow-up commit of ' + hash_of_first_commit
hash_of_followup = self._make_empty_commit(message)
merges = mergeinfo.get_merge_commits(self.base_dir, hash_of_first_commit)
# Check if follow up is ignored
self.assertTrue(hash_of_followup not in merges)
# Check for proper return of merges
self.assertTrue(hash_of_hit in merges)
self.assertTrue(hash_of_ignored not in merges)
def testIsLkgr(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
self._make_empty_commit('This one is the lkgr head')
self._execute_git(['branch', 'remotes/origin/lkgr'])
hash_of_not_lkgr = self._make_empty_commit('This one is not yet lkgr')
branches = self._get_branches(hash_of_first_commit);
self.assertTrue(mergeinfo.is_lkgr(branches))
branches = self._get_branches(hash_of_not_lkgr);
self.assertFalse(mergeinfo.is_lkgr(branches))
def testShowFirstCanary(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
branches = self._get_branches(hash_of_first_commit);
self.assertEqual(mergeinfo.get_first_canary(branches), 'No Canary coverage')
self._execute_git(['branch', 'remotes/origin/chromium/2345'])
self._execute_git(['branch', 'remotes/origin/chromium/2346'])
branches = self._get_branches(hash_of_first_commit);
self.assertEqual(mergeinfo.get_first_canary(branches), '2345')
def testFirstV8Version(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
self._execute_git(['branch', 'remotes/origin/chromium/2345'])
self._execute_git(['branch', 'remotes/origin/chromium/2346'])
branches = self._get_branches(hash_of_first_commit);
self.assertEqual(mergeinfo.get_first_v8_version(branches), '--')
self._execute_git(['branch', 'remotes/origin/5.7.1'])
self._execute_git(['branch', 'remotes/origin/5.8.1'])
branches = self._get_branches(hash_of_first_commit);
self.assertEqual(mergeinfo.get_first_v8_version(branches), '5.7.1')
self._execute_git(['branch', 'remotes/origin/5.6.1'])
branches = self._get_branches(hash_of_first_commit);
self.assertEqual(mergeinfo.get_first_v8_version(branches), '5.6.1')
if __name__ == "__main__":
unittest.main()

797
deps/v8/tools/release/test_scripts.py vendored Executable file
View File

@ -0,0 +1,797 @@
#!/usr/bin/env python3
# Copyright 2013 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import json
import os
import shutil
import tempfile
import traceback
import unittest
import auto_push
from auto_push import LastReleaseBailout
import common_includes
from common_includes import *
import create_release
from create_release import *
import merge_to_branch
from merge_to_branch import MergeToBranch
import roll_merge
from roll_merge import RollMerge
TEST_CONFIG = {
"DEFAULT_CWD": None,
"BRANCHNAME": "test-prepare-push",
"PERSISTFILE_BASENAME": "/tmp/test-create-releases-tempfile",
"PATCH_FILE": "/tmp/test-v8-create-releases-tempfile-tempfile-patch",
"COMMITMSG_FILE": "/tmp/test-v8-create-releases-tempfile-commitmsg",
"CHROMIUM": "/tmp/test-create-releases-tempfile-chromium",
"SETTINGS_LOCATION": None,
"ALREADY_MERGING_SENTINEL_FILE":
"/tmp/test-merge-to-branch-tempfile-already-merging",
"TEMPORARY_PATCH_FILE": "/tmp/test-merge-to-branch-tempfile-temporary-patch",
}
AUTO_PUSH_ARGS = [
"-a", "author@chromium.org",
"-r", "reviewer@chromium.org",
]
class ToplevelTest(unittest.TestCase):
def testSaniniziteVersionTags(self):
self.assertEquals("4.8.230", SanitizeVersionTag("4.8.230"))
self.assertEquals("4.8.230", SanitizeVersionTag("tags/4.8.230"))
self.assertEquals(None, SanitizeVersionTag("candidate"))
def testNormalizeVersionTags(self):
input = ["4.8.230",
"tags/4.8.230",
"tags/4.8.224.1",
"4.8.224.1",
"4.8.223.1",
"tags/4.8.223",
"tags/4.8.231",
"candidates"]
expected = ["4.8.230",
"4.8.230",
"4.8.224.1",
"4.8.224.1",
"4.8.223.1",
"4.8.223",
"4.8.231",
]
self.assertEquals(expected, NormalizeVersionTags(input))
def testCommand(self):
"""Ensure json can decode the output of commands."""
json.dumps(Command('ls', pipe=True))
def Cmd(*args, **kwargs):
"""Convenience function returning a shell command test expectation."""
return {
"name": "command",
"args": args,
"ret": args[-1],
"cb": kwargs.get("cb"),
"cwd": kwargs.get("cwd", TEST_CONFIG["DEFAULT_CWD"]),
}
def RL(text, cb=None):
"""Convenience function returning a readline test expectation."""
return {
"name": "readline",
"args": [],
"ret": text,
"cb": cb,
"cwd": None,
}
def URL(*args, **kwargs):
"""Convenience function returning a readurl test expectation."""
return {
"name": "readurl",
"args": args[:-1],
"ret": args[-1],
"cb": kwargs.get("cb"),
"cwd": None,
}
class SimpleMock(object):
def __init__(self):
self._recipe = []
self._index = -1
def Expect(self, recipe):
self._recipe = recipe
def Call(self, name, *args, **kwargs): # pragma: no cover
self._index += 1
try:
expected_call = self._recipe[self._index]
except IndexError:
raise NoRetryException("Calling %s %s" % (name, " ".join(args)))
if not isinstance(expected_call, dict):
raise NoRetryException("Found wrong expectation type for %s %s" %
(name, " ".join(args)))
if expected_call["name"] != name:
raise NoRetryException("Expected action: %s %s - Actual: %s" %
(expected_call["name"], expected_call["args"], name))
# Check if the given working directory matches the expected one.
if expected_call["cwd"] != kwargs.get("cwd"):
raise NoRetryException("Expected cwd: %s in %s %s - Actual: %s" %
(expected_call["cwd"],
expected_call["name"],
expected_call["args"],
kwargs.get("cwd")))
# The number of arguments in the expectation must match the actual
# arguments.
if len(args) > len(expected_call['args']):
raise NoRetryException("When calling %s with arguments, the "
"expectations must consist of at least as many arguments." %
name)
# Compare expected and actual arguments.
for (expected_arg, actual_arg) in zip(expected_call['args'], args):
if expected_arg != actual_arg:
raise NoRetryException("Expected: %s - Actual: %s" %
(expected_arg, actual_arg))
# The expected call contains an optional callback for checking the context
# at the time of the call.
if expected_call['cb']:
try:
expected_call['cb']()
except:
tb = traceback.format_exc()
raise NoRetryException("Caught exception from callback: %s" % tb)
# If the return value is an exception, raise it instead of returning.
if isinstance(expected_call['ret'], Exception):
raise expected_call['ret']
return expected_call['ret']
def AssertFinished(self): # pragma: no cover
if self._index < len(self._recipe) -1:
raise NoRetryException("Called mock too seldom: %d vs. %d" %
(self._index, len(self._recipe)))
class ScriptTest(unittest.TestCase):
def MakeEmptyTempFile(self):
handle, name = tempfile.mkstemp()
os.close(handle)
self._tmp_files.append(name)
return name
def MakeEmptyTempDirectory(self):
name = tempfile.mkdtemp()
self._tmp_files.append(name)
return name
def WriteFakeVersionFile(self, major=3, minor=22, build=4, patch=0):
version_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE)
if not os.path.exists(os.path.dirname(version_file)):
os.makedirs(os.path.dirname(version_file))
with open(version_file, "w") as f:
f.write(" // Some line...\n")
f.write("\n")
f.write("#define V8_MAJOR_VERSION %s\n" % major)
f.write("#define V8_MINOR_VERSION %s\n" % minor)
f.write("#define V8_BUILD_NUMBER %s\n" % build)
f.write("#define V8_PATCH_LEVEL %s\n" % patch)
f.write(" // Some line...\n")
f.write("#define V8_IS_CANDIDATE_VERSION 0\n")
def WriteFakeWatchlistsFile(self):
watchlists_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], WATCHLISTS_FILE)
if not os.path.exists(os.path.dirname(watchlists_file)):
os.makedirs(os.path.dirname(watchlists_file))
with open(watchlists_file, "w") as f:
content = """
'merges': [
# Only enabled on branches created with tools/release/create_release.py
# 'v8-merges@googlegroups.com',
],
"""
f.write(content)
def MakeStep(self):
"""Convenience wrapper."""
options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
return MakeStep(step_class=Step, state=self._state,
config=TEST_CONFIG, side_effect_handler=self,
options=options)
def RunStep(self, script=CreateRelease, step_class=Step, args=None):
"""Convenience wrapper."""
args = args if args is not None else ["-m", "-a=author", "-r=reviewer", ]
return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
def Call(self, fun, *args, **kwargs):
print("Calling %s with %s and %s" % (str(fun), str(args), str(kwargs)))
def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
print("%s %s" % (cmd, args))
print("in %s" % cwd)
return self._mock.Call("command", cmd + " " + args, cwd=cwd)
def ReadLine(self):
return self._mock.Call("readline")
def ReadURL(self, url, params):
if params is not None:
return self._mock.Call("readurl", url, params)
else:
return self._mock.Call("readurl", url)
def Sleep(self, seconds):
pass
def GetUTCStamp(self):
return "1000000"
def Expect(self, *args):
"""Convenience wrapper."""
self._mock.Expect(*args)
def setUp(self):
self._mock = SimpleMock()
self._tmp_files = []
self._state = {}
TEST_CONFIG["DEFAULT_CWD"] = self.MakeEmptyTempDirectory()
def tearDown(self):
if os.path.exists(TEST_CONFIG["PERSISTFILE_BASENAME"]):
shutil.rmtree(TEST_CONFIG["PERSISTFILE_BASENAME"])
# Clean up temps. Doesn't work automatically.
for name in self._tmp_files:
if os.path.isfile(name):
os.remove(name)
if os.path.isdir(name):
shutil.rmtree(name)
self._mock.AssertFinished()
def testGitMock(self):
self.Expect([Cmd("git --version", "git version 1.2.3"),
Cmd("git dummy", "")])
self.assertEquals("git version 1.2.3", self.MakeStep().Git("--version"))
self.assertEquals("", self.MakeStep().Git("dummy"))
def testCommonPrepareDefault(self):
self.Expect([
Cmd("git status -s -uno", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git fetch", ""),
Cmd("git branch", " branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
RL("Y"),
Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
])
self.MakeStep().CommonPrepare()
self.MakeStep().PrepareBranch()
def testCommonPrepareNoConfirm(self):
self.Expect([
Cmd("git status -s -uno", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git fetch", ""),
Cmd("git branch", " branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
RL("n"),
])
self.MakeStep().CommonPrepare()
self.assertRaises(Exception, self.MakeStep().PrepareBranch)
def testCommonPrepareDeleteBranchFailure(self):
self.Expect([
Cmd("git status -s -uno", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git fetch", ""),
Cmd("git branch", " branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
RL("Y"),
Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], None),
])
self.MakeStep().CommonPrepare()
self.assertRaises(Exception, self.MakeStep().PrepareBranch)
def testInitialEnvironmentChecks(self):
TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
os.environ["EDITOR"] = "vi"
self.Expect([
Cmd("which vi", "/usr/bin/vi"),
])
self.MakeStep().InitialEnvironmentChecks(TEST_CONFIG["DEFAULT_CWD"])
def testTagTimeout(self):
self.Expect([
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
])
args = ["--branch", "candidates", "ab12345"]
self._state["version"] = "tag_name"
self._state["commit_title"] = "Title"
self.assertRaises(Exception,
lambda: self.RunStep(RollMerge, TagRevision, args))
def testReadAndPersistVersion(self):
self.WriteFakeVersionFile(build=5)
step = self.MakeStep()
step.ReadAndPersistVersion()
self.assertEquals("3", step["major"])
self.assertEquals("22", step["minor"])
self.assertEquals("5", step["build"])
self.assertEquals("0", step["patch"])
def testRegex(self):
self.assertEqual("(issue 321)",
re.sub(r"BUG=v8:(.*)$", r"(issue \1)", "BUG=v8:321"))
self.assertEqual("(Chromium issue 321)",
re.sub(r"BUG=(.*)$", r"(Chromium issue \1)", "BUG=321"))
cl = " too little\n\ttab\ttab\n too much\n trailing "
cl = MSub(r"\t", r" ", cl)
cl = MSub(r"^ {1,7}([^ ])", r" \1", cl)
cl = MSub(r"^ {9,80}([^ ])", r" \1", cl)
cl = MSub(r" +$", r"", cl)
self.assertEqual(" too little\n"
" tab tab\n"
" too much\n"
" trailing", cl)
self.assertEqual("//\n#define V8_BUILD_NUMBER 3\n",
MSub(r"(?<=#define V8_BUILD_NUMBER)(?P<space>\s+)\d*$",
r"\g<space>3",
"//\n#define V8_BUILD_NUMBER 321\n"))
TAGS = """
4425.0
0.0.0.0
3.9.6
3.22.4
test_tag
"""
# Version as tag: 3.22.4.0. Version on main: 3.22.6.
# Make sure that the latest version is 3.22.6.0.
def testIncrementVersion(self):
self.Expect([
Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
Cmd("git tag", self.TAGS),
Cmd("git checkout -f origin/main -- include/v8-version.h",
"", cb=lambda: self.WriteFakeVersionFile(3, 22, 6)),
])
self.RunStep(CreateRelease, IncrementVersion)
self.assertEquals("3", self._state["new_major"])
self.assertEquals("22", self._state["new_minor"])
self.assertEquals("7", self._state["new_build"])
self.assertEquals("0", self._state["new_patch"])
def testBootstrapper(self):
work_dir = self.MakeEmptyTempDirectory()
class FakeScript(ScriptsBase):
def _Steps(self):
return []
# Use the test configuration without the fake testing default work dir.
fake_config = dict(TEST_CONFIG)
del(fake_config["DEFAULT_CWD"])
self.Expect([
Cmd("fetch v8", "", cwd=work_dir),
])
FakeScript(fake_config, self).Run(["--work-dir", work_dir])
def testCreateRelease(self):
TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
# The version file on main has build level 5.
self.WriteFakeVersionFile(build=5)
commit_msg = """Version 3.22.5"""
def CheckVersionCommit():
commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
self.assertEquals(commit_msg, commit)
version = FileToText(
os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
self.assertFalse(re.search(r"#define V8_BUILD_NUMBER\s+6", version))
self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+0", version))
self.assertTrue(
re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
expectations = [
Cmd("git fetch origin +refs/heads/*:refs/heads/*", ""),
Cmd("git checkout -f origin/main", "", cb=self.WriteFakeWatchlistsFile),
Cmd("git branch", ""),
Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
Cmd("git tag", self.TAGS),
Cmd("git checkout -f origin/main -- include/v8-version.h",
"", cb=self.WriteFakeVersionFile),
Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
Cmd("git log -1 --format=%s release_hash", "Version 3.22.4\n"),
Cmd("git log -1 --format=%H release_hash^", "abc3\n"),
Cmd("git log --format=%H abc3..push_hash", "rev1\n"),
Cmd("git push origin push_hash:refs/heads/3.22.5", ""),
Cmd("git reset --hard origin/main", ""),
Cmd("git new-branch work-branch --upstream origin/3.22.5", ""),
Cmd("git checkout -f 3.22.4 -- include/v8-version.h", "",
cb=self.WriteFakeVersionFile),
Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], "",
cb=CheckVersionCommit),
Cmd("git cl upload --send-mail "
"-f --set-bot-commit --bypass-hooks --no-autocc --message-file "
"\"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
Cmd("git cl land --bypass-hooks -f", ""),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep="
"\"Version 3.22.5\" origin/3.22.5", "hsh_to_tag"),
Cmd("git tag 3.22.5 hsh_to_tag", ""),
Cmd("git push origin refs/tags/3.22.5:refs/tags/3.22.5", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git branch", "* main\n work-branch\n"),
Cmd("git branch -D work-branch", ""),
Cmd("git gc", ""),
]
self.Expect(expectations)
args = ["-a", "author@chromium.org",
"-r", "reviewer@chromium.org",
"--revision", "push_hash"]
CreateRelease(TEST_CONFIG, self).Run(args)
# Note: The version file is on build number 5 again in the end of this test
# since the git command that merges to main is mocked out.
# Check for correct content of the WATCHLISTS file
watchlists_content = FileToText(os.path.join(TEST_CONFIG["DEFAULT_CWD"],
WATCHLISTS_FILE))
expected_watchlists_content = """
'merges': [
# Only enabled on branches created with tools/release/create_release.py
'v8-merges@googlegroups.com',
],
"""
self.assertEqual(watchlists_content, expected_watchlists_content)
def testCheckLastPushRecently(self):
self.Expect([
Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
Cmd("git tag", self.TAGS),
Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
Cmd("git log -1 --format=%s release_hash",
"Version 3.22.4 (based on abc3)\n"),
Cmd("git log --format=%H abc3..abc123", "\n"),
])
self._state["candidate"] = "abc123"
self.assertEquals(0, self.RunStep(
auto_push.AutoPush, LastReleaseBailout, AUTO_PUSH_ARGS))
def testAutoPush(self):
self.Expect([
Cmd("git fetch", ""),
Cmd("git fetch origin +refs/heads/lkgr:refs/heads/lkgr", ""),
Cmd("git show-ref -s refs/heads/lkgr", "abc123\n"),
Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
Cmd("git tag", self.TAGS),
Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
Cmd("git log -1 --format=%s release_hash",
"Version 3.22.4 (based on abc3)\n"),
Cmd("git log --format=%H abc3..abc123", "some_stuff\n"),
])
auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS + ["--push"])
state = json.loads(FileToText("%s-state.json"
% TEST_CONFIG["PERSISTFILE_BASENAME"]))
self.assertEquals("abc123", state["candidate"])
def testRollMerge(self):
TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
self.WriteFakeVersionFile(build=5)
os.environ["EDITOR"] = "vi"
extra_patch = self.MakeEmptyTempFile()
def VerifyPatch(patch):
return lambda: self.assertEquals(patch,
FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"]))
msg = """Version 3.22.5.1 (cherry-pick)
Merged ab12345
Merged ab23456
Merged ab34567
Merged ab45678
Merged ab56789
Title4
Title2
Title3
Title1
Revert "Something"
BUG=123,234,345,456,567,v8:123
"""
def VerifyLand():
commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
self.assertEquals(msg, commit)
version = FileToText(
os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+1", version))
self.assertTrue(
re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
self.Expect([
Cmd("git status -s -uno", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git fetch", ""),
Cmd("git branch", " branch1\n* branch2\n"),
Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" %
TEST_CONFIG["BRANCHNAME"], ""),
Cmd(("git log --format=%H --grep=\"Port ab12345\" "
"--reverse origin/main"),
"ab45678\nab23456"),
Cmd("git log -1 --format=%s ab45678", "Title1"),
Cmd("git log -1 --format=%s ab23456", "Title2"),
Cmd(("git log --format=%H --grep=\"Port ab23456\" "
"--reverse origin/main"),
""),
Cmd(("git log --format=%H --grep=\"Port ab34567\" "
"--reverse origin/main"),
"ab56789"),
Cmd("git log -1 --format=%s ab56789", "Title3"),
RL("Y"), # Automatically add corresponding ports (ab34567, ab56789)?
# Simulate git being down which stops the script.
Cmd("git log -1 --format=%s ab12345", None),
# Restart script in the failing step.
Cmd("git log -1 --format=%s ab12345", "Title4"),
Cmd("git log -1 --format=%s ab23456", "Title2"),
Cmd("git log -1 --format=%s ab34567", "Title3"),
Cmd("git log -1 --format=%s ab45678", "Title1"),
Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""),
Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"),
Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"),
Cmd("git log -1 ab34567", "Title3\nBUG=567, 456"),
Cmd("git log -1 ab45678", "Title1\nBUG="),
Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"),
Cmd("git log -1 -p ab12345", "patch4"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch4")),
Cmd("git log -1 -p ab23456", "patch2"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch2")),
Cmd("git log -1 -p ab34567", "patch3"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch3")),
Cmd("git log -1 -p ab45678", "patch1"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch1")),
Cmd("git log -1 -p ab56789", "patch5\n"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch5\n")),
Cmd("git apply --index --reject \"%s\"" % extra_patch, ""),
RL("Y"), # Automatically increment patch level?
Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
RL("reviewer@chromium.org"), # V8 reviewer.
Cmd("git cl upload --send-mail "
"-r \"reviewer@chromium.org\" --bypass-hooks", ""),
Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""),
RL("LGTM"), # Enter LGTM for V8 CL.
Cmd("git cl presubmit", "Presubmit successfull\n"),
Cmd("git cl land -f --bypass-hooks", "Closing issue\n",
cb=VerifyLand),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep=\""
"Version 3.22.5.1 (cherry-pick)"
"\" refs/remotes/origin/candidates",
""),
Cmd("git fetch", ""),
Cmd("git log -1 --format=%H --grep=\""
"Version 3.22.5.1 (cherry-pick)"
"\" refs/remotes/origin/candidates",
"hsh_to_tag"),
Cmd("git tag 3.22.5.1 hsh_to_tag", ""),
Cmd("git push origin refs/tags/3.22.5.1:refs/tags/3.22.5.1", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
])
# ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the
# MIPS ports of ab12345. ab56789 is the MIPS port of ab34567.
args = ["-f", "-p", extra_patch, "--branch", "candidates",
"ab12345", "ab23456", "ab34567"]
# The first run of the script stops because of git being down.
self.assertRaises(GitFailedException,
lambda: RollMerge(TEST_CONFIG, self).Run(args))
# Test that state recovery after restarting the script works.
args += ["-s", "4"]
RollMerge(TEST_CONFIG, self).Run(args)
def testMergeToBranch(self):
TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
self.WriteFakeVersionFile(build=5)
os.environ["EDITOR"] = "vi"
extra_patch = self.MakeEmptyTempFile()
def VerifyPatch(patch):
return lambda: self.assertEquals(patch,
FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"]))
info_msg = ("NOTE: This script will no longer automatically "
"update include/v8-version.h "
"and create a tag. This is done automatically by the autotag bot. "
"Please call the merge_to_branch.py with --help for more information.")
msg = """Merged: Squashed multiple commits.
Merged: Title4
Revision: ab12345
Merged: Title2
Revision: ab23456
Merged: Title3
Revision: ab34567
Merged: Title1
Revision: ab45678
Merged: Revert \"Something\"
Revision: ab56789
BUG=123,234,345,456,567,v8:123
"""
def VerifyLand():
commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
self.assertEquals(msg, commit)
self.Expect([
Cmd("git status -s -uno", ""),
Cmd("git checkout -f origin/main", ""),
Cmd("git fetch", ""),
Cmd("git branch", " branch1\n* branch2\n"),
Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" %
TEST_CONFIG["BRANCHNAME"], ""),
Cmd(("git log --format=%H --grep=\"^[Pp]ort ab12345\" "
"--reverse origin/main"),
"ab45678\nab23456"),
Cmd("git log -1 --format=%s ab45678", "Title1"),
Cmd("git log -1 --format=%s ab23456", "Title2"),
Cmd(("git log --format=%H --grep=\"^[Pp]ort ab23456\" "
"--reverse origin/main"),
""),
Cmd(("git log --format=%H --grep=\"^[Pp]ort ab34567\" "
"--reverse origin/main"),
"ab56789"),
Cmd("git log -1 --format=%s ab56789", "Title3"),
RL("Y"), # Automatically add corresponding ports (ab34567, ab56789)?
# Simulate git being down which stops the script.
Cmd("git log -1 --format=%s ab12345", None),
# Restart script in the failing step.
Cmd("git log -1 --format=%s ab12345", "Title4"),
Cmd("git log -1 --format=%s ab23456", "Title2"),
Cmd("git log -1 --format=%s ab34567", "Title3"),
Cmd("git log -1 --format=%s ab45678", "Title1"),
Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""),
Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"),
Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"),
Cmd("git log -1 ab34567", "Title3\nBug: 567, 456,345"),
Cmd("git log -1 ab45678", "Title1\nBug:"),
Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"),
Cmd("git log -1 -p ab12345", "patch4"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch4")),
Cmd("git log -1 -p ab23456", "patch2"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch2")),
Cmd("git log -1 -p ab34567", "patch3"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch3")),
Cmd("git log -1 -p ab45678", "patch1"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch1")),
Cmd("git log -1 -p ab56789", "patch5\n"),
Cmd(("git apply --index --reject \"%s\"" %
TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
"", cb=VerifyPatch("patch5\n")),
Cmd("git apply --index --reject \"%s\"" % extra_patch, ""),
Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
RL("reviewer@chromium.org"), # V8 reviewer.
Cmd("git cl upload --send-mail "
"-r \"reviewer@chromium.org\" --bypass-hooks", ""),
Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""),
RL("LGTM"), # Enter LGTM for V8 CL.
Cmd("git cl presubmit", "Presubmit successfull\n"),
Cmd("git cl land -f --bypass-hooks", "Closing issue\n",
cb=VerifyLand),
Cmd("git checkout -f origin/main", ""),
Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
])
# ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the
# MIPS ports of ab12345. ab56789 is the MIPS port of ab34567.
args = ["-f", "-p", extra_patch, "--branch", "candidates",
"ab12345", "ab23456", "ab34567"]
# The first run of the script stops because of git being down.
self.assertRaises(GitFailedException,
lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
# Test that state recovery after restarting the script works.
args += ["-s", "4"]
MergeToBranch(TEST_CONFIG, self).Run(args)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,28 @@
Looking for latest roll CL...
Found roll CL:
> V8 roll from 123abc to 456def
>
> https://chromium.googlesource.com/v8/v8.git/+log/123abc..456def
>
> 2038-01-19 v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com Version 1.2.3
Cherry-picking deadbeef onto 1.2.3
Created cherry-pick: https://chromium-review.googlesource.com/c/42
Extracting target version...
... version is {'major': 1, 'minor': 2, 'build': 3, 'patch': 0}
Setting 'roll-merge' hashtag...
Updating include/v8-version.h to patch level 1...
Updating commit message...
Publishing changes...
Setting 'Owners-Override +1'...
Adding Rubber Stamper as a reviewer...
Cherry-pick 1.2.3.1 created successfully: https://chromium-review.googlesource.com/c/42
Waiting for Code-Review +1 or Bot-Commit +1...
Submitting...
Found committed as deadbeefce...
Setting 1.2.3.1 tag...
Waiting until it's safe to reopen rolling, might take 2-3 minutes...
- waiting time: 0:00:00
It's safe now to reopen the auto roller at https://autoroll.skia.org/r/v8-chromium-autoroll.
Waiting for PGO profile tag (1.2.3.1-pgo), this can take 15-20 minutes...
- waiting time: 0:00:00
Done.