blob: 27ebcd1c8f1d99790d20db63d4ed9f7920c1b795 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Merge Skia into Android."""
import os
import sys
from build_step import BuildStep, BuildStepFailure, BuildStepWarning
import skia_vars
from sync_android import ANDROID_CHECKOUT_PATH, REPO, GitAuthenticate
from utils.git_utils import GIT
from utils import git_utils
from utils import misc
from utils import shell_utils
SKIA_REPO_URL = skia_vars.GetGlobalVariable('skia_git_url')
SKIA_REV_URL = skia_vars.GetGlobalVariable('revlink_tmpl')
MASTER_SKIA_URL = ('https://googleplex-android-review.googlesource.com/'
'platform/external/skia')
MASTER_SKIA_REFS = 'HEAD:refs/heads/master-skia'
UPSTREAM_REMOTE_NAME = 'upstream'
ANDROID_USER_CONFIG = 'include/core/SkUserConfig.h'
UPSTREAM_USER_CONFIG = 'include/config/SkUserConfig.h'
EXTERNAL_SKIA = os.path.join(ANDROID_CHECKOUT_PATH, 'external', 'skia')
# Path to gyp_to_android.py.
PLATFORM_TOOLS_BIN = os.path.join(EXTERNAL_SKIA, 'platform_tools', 'android',
'bin')
sys.path.append(PLATFORM_TOOLS_BIN)
import gyp_to_android
LOCAL_BRANCH_NAME = 'merge'
def RepoAbandon(branch):
"""Run 'repo abandon <branch>'
'repo abandon' is similar to 'git branch -D', and is only necessary after
a branch created by 'repo start' is no longer needed."""
shell_utils.run([REPO, 'abandon', branch])
class MergeIntoAndroid(BuildStep):
"""BuildStep which merges Skia into Android, with a generated Android.mk and
SkUserConfig.h"""
def _Run(self):
with misc.ChDir(EXTERNAL_SKIA):
# Check to see whether there is an upstream yet.
if not UPSTREAM_REMOTE_NAME in shell_utils.run([GIT, 'remote', 'show']):
try:
shell_utils.run([GIT, 'remote', 'add', UPSTREAM_REMOTE_NAME,
SKIA_REPO_URL])
except shell_utils.CommandFailedException as e:
if 'remote %s already exists' % UPSTREAM_REMOTE_NAME in e.output:
# Accept this error. The upstream remote name should have been in
# the output of git remote show, which would have made us skip this
# redundant command anyway.
print ('%s was already added. Why did it not show in git remote'
' show?' % UPSTREAM_REMOTE_NAME)
else:
raise e
# Update the upstream remote.
shell_utils.run([GIT, 'fetch', UPSTREAM_REMOTE_NAME])
# Create a stack of commits to submit, one at a time, until we reach a
# commit that has already been merged.
commit_stack = []
head = git_utils.ShortHash('HEAD')
print 'HEAD is at %s' % head
if self._got_revision:
# Merge the revision that started this build.
commit = git_utils.ShortHash(self._got_revision)
else:
raise Exception('This build has no _got_revision to merge!')
print ('Starting with %s, look for commits that have not been merged to '
'HEAD' % commit)
while not git_utils.AIsAncestorOfB(commit, head):
print 'Adding %s to list of commits to merge.' % commit
commit_stack.append(commit)
if git_utils.IsMerge(commit):
# Skia's commit history is not linear. There is no obvious way to
# merge each branch in, one commit at a time. So just start with the
# merge commit.
print '%s is a merge. Skipping merge of its parents.' % commit
break
commit = git_utils.ShortHash(commit + '~1')
else:
print '%s has already been merged.' % commit
if len(commit_stack) == 0:
raise BuildStepWarning('Nothing to merge; did someone already merge %s?'
' Exiting.' % commit)
print 'Merging %s commit(s):\n%s' % (len(commit_stack),
'\n'.join(reversed(commit_stack)))
# Now we have a list of commits to merge.
while len(commit_stack) > 0:
commit_to_merge = commit_stack.pop()
print 'Attempting to merge ' + commit_to_merge
# Start the merge.
try:
shell_utils.run([GIT, 'merge', commit_to_merge, '--no-commit'])
except shell_utils.CommandFailedException:
# Merge conflict. There may be a more elegant solution, but for now,
# undo the merge, and allow (/make) a human to do it.
git_utils.MergeAbort()
raise Exception('Failed to merge %s. Fall back to manual human '
'merge.' % commit_to_merge)
# Grab the upstream version of SkUserConfig, which will be used to
# generate Android's version.
shell_utils.run([GIT, 'checkout', commit_to_merge, '--',
UPSTREAM_USER_CONFIG])
# We don't want to commit the upstream version, so remove it from the
# index.
shell_utils.run([GIT, 'reset', 'HEAD', UPSTREAM_USER_CONFIG])
# Now generate Android.mk and SkUserConfig.h
try:
gyp_to_android.main()
except AssertionError as e:
print e
# Failed to generate the makefiles. Make a human fix the problem.
git_utils.MergeAbort()
raise Exception('Failed to generate makefiles for %s. Fall back to '
'manual human merge.' % commit_to_merge)
git_utils.Add('Android.mk')
git_utils.Add(ANDROID_USER_CONFIG)
git_utils.Add(os.path.join('tests', 'Android.mk'))
git_utils.Add(os.path.join('bench', 'Android.mk'))
git_utils.Add(os.path.join('gm', 'Android.mk'))
git_utils.Add(os.path.join('dm', 'Android.mk'))
# Remove upstream user config, which is no longer needed.
os.remove(UPSTREAM_USER_CONFIG)
# Create a new branch.
shell_utils.run([REPO, 'start', LOCAL_BRANCH_NAME, '.'])
try:
orig_msg = shell_utils.run([GIT, 'show', commit_to_merge,
'--format="%s"', '-s']).rstrip()
message = 'Merge %s into master-skia\n\n' + SKIA_REV_URL
shell_utils.run([GIT, 'commit', '-m', message % (orig_msg,
commit_to_merge)])
except shell_utils.CommandFailedException:
# It is possible that someone else already did the merge (for example,
# if they are testing a build slave). Clean up and exit.
RepoAbandon(LOCAL_BRANCH_NAME)
raise BuildStepWarning('Nothing to merge; did someone already merge '
'%s?' % commit_to_merge)
# For some reason, sometimes the bot's authentication from sync_android
# does not carry over to this step. Authenticate again.
with GitAuthenticate():
# Now push to master-skia branch
try:
shell_utils.run([GIT, 'push', MASTER_SKIA_URL, MASTER_SKIA_REFS])
except shell_utils.CommandFailedException:
# It's possible someone submitted in between our sync and push or
# push failed for some other reason. Abandon and let the next
# attempt try again.
RepoAbandon(LOCAL_BRANCH_NAME)
raise BuildStepFailure('git push failed!')
# Our branch is no longer needed. Remove it.
shell_utils.run([REPO, 'sync', '-j32', '.'])
shell_utils.run([REPO, 'prune', '.'])
if '__main__' == __name__:
sys.exit(BuildStep.RunBuildStep(MergeIntoAndroid))