blob: 230f1ea8b6eb62252bf4dd2b584a68d2248d935e [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Update the CHROMEOS_LKGM file in a chromium repository.
Note: this borrows heavily from cros_best_revision.py. TODO(stevenjb) Remove
cros_best_revision.py once this replaces it and the Chrome LKGM builder is
turned down.
"""
from __future__ import print_function
import distutils.version
import os
from chromite.cbuildbot import manifest_version
from chromite.lib import tree_status
from chromite.lib import constants
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import cros_logging as logging
from chromite.lib import gclient
from chromite.lib import git
from chromite.lib import osutils
from chromite.lib import retry_util
class LKGMNotValid(Exception):
"""Raised if the LKGM version is unset or not newer than the current value."""
class LKGMNotCommitted(Exception):
"""Raised if we could not submit a new LKGM."""
class ChromeLGTMCommitter(object):
"""Committer object responsible for obtaining a new LKGM and committing it."""
_COMMIT_MSG_TEMPLATE = ('Automated Commit: Committing new LKGM version '
'%(version)s for chromeos.')
_SLEEP_TIMEOUT = 30
_TREE_TIMEOUT = 7200
def __init__(self, checkout_dir, lkgm, dryrun, user_email):
self._checkout_dir = checkout_dir
# Strip any chrome branch from the lkgm version.
self._lkgm = manifest_version.VersionInfo(lkgm).VersionString()
self._dryrun = dryrun
self._git_committer_args = ['-c', 'user.email=%s' % user_email,
'-c', 'user.name=%s' % user_email]
self._commit_msg = ''
self._old_lkgm = None
def CheckoutChromeLKGM(self):
"""Checkout CHROMEOS_LKGM file for chrome into tmp checkout dir."""
# TODO(stevenjb): if checkout_dir exists, use 'fetch --depth 1' and
# 'checkout -f origin/master' instead of 'clone --depth 1'.
osutils.RmDir(self._checkout_dir, ignore_missing=True)
cros_build_lib.RunCommand(
['git', 'clone', '--depth', '1', constants.CHROMIUM_GOB_URL,
self._checkout_dir])
cros_build_lib.RunCommand(
['git', 'branch', '-D', 'lkgm-roll'], cwd=self._checkout_dir,
error_code_ok=True)
cros_build_lib.RunCommand(
['git', 'checkout', '-b', 'lkgm-roll', 'origin/master'],
cwd=self._checkout_dir)
self._old_lkgm = osutils.ReadFile(
os.path.join(self._checkout_dir, constants.PATH_TO_CHROME_LKGM))
def CommitNewLKGM(self):
"""Commits the new LKGM file using our template commit message."""
if not self._lkgm:
raise LKGMNotValid('LKGM not provided.')
lv = distutils.version.LooseVersion
if self._old_lkgm is not None and not lv(self._lkgm) > lv(self._old_lkgm):
raise LKGMNotValid('LKGM version is not newer than current version.')
logging.info('Committing LKGM version: %s (was %s),',
self._lkgm, self._old_lkgm)
self._commit_msg = self._COMMIT_MSG_TEMPLATE % dict(version=self._lkgm)
checkout_dir = self._checkout_dir
try:
# Overwrite the lkgm file and commit it.
file_path = os.path.join(checkout_dir, constants.PATH_TO_CHROME_LKGM)
osutils.WriteFile(file_path, self._lkgm)
git.AddPath(file_path)
commit_args = ['commit', '-m', self._commit_msg]
git.RunGit(checkout_dir, self._git_committer_args + commit_args,
print_cmd=True, redirect_stderr=True, capture_output=False)
except cros_build_lib.RunCommandError as e:
raise LKGMNotCommitted(
'Could not create git commit with new LKGM: %r' % e)
def UploadNewLKGM(self):
"""Uploads the change to gerrit."""
logging.info('Uploading LKGM commit.')
try:
# Run 'git cl upload' with --bypass-hooks to skip running scripts that are
# not part of the shallow checkout, -f to skip editing the CL message,
# --send-mail to mark the CL as ready, and --tbrs to +1 the CL.
upload_args = ['cl', 'upload', '-v', '-m', self._commit_msg,
'--bypass-hooks', '-f']
if not self._dryrun:
upload_args += ['--send-mail', '--tbrs=chrome-os-gardeners@google.com']
git.RunGit(self._checkout_dir, self._git_committer_args + upload_args,
print_cmd=True, redirect_stderr=True, capture_output=False)
except cros_build_lib.RunCommandError as e:
# Log the change for debugging.
cros_build_lib.RunCommand(['git', 'log', '--pretty=full'],
cwd=self._checkout_dir)
raise LKGMNotCommitted('Could not submit LKGM: upload failed: %r' % e)
def _TryLandNewLKGM(self):
"""Fetches latest, rebases the CL, and lands the rebased CL."""
git.RunGit(self._checkout_dir, ['fetch', 'origin', 'master'])
try:
git.RunGit(self._checkout_dir, ['rebase'], retry=False)
except cros_build_lib.RunCommandError as e:
# A rebase failure was unexpected, so raise a custom LKGMNotCommitted
# error to avoid further retries.
git.RunGit(self._checkout_dir, ['rebase', '--abort'])
raise LKGMNotCommitted('Could not submit LKGM: rebase failed: %r' % e)
if self._dryrun:
logging.info('Dry run; rebase succeeded, exiting.')
return
git.RunGit(self._checkout_dir, ['cl', 'land', '-f', '--bypass-hooks'])
def LandNewLKGM(self, max_retry=10):
"""Lands the change after fetching and rebasing."""
if not tree_status.IsTreeOpen(status_url=gclient.STATUS_URL,
period=self._SLEEP_TIMEOUT,
timeout=self._TREE_TIMEOUT):
raise LKGMNotCommitted('Chromium Tree is closed')
logging.info('Landing LKGM commit.')
# git cl land refuses to land a change that isn't relative to ToT, so any
# new commits since the last fetch will cause this to fail. Retries should
# make this edge case ultimately unlikely.
try:
retry_util.RetryCommand(self._TryLandNewLKGM,
max_retry,
sleep=self._SLEEP_TIMEOUT,
log_all_retries=True)
except cros_build_lib.RunCommandError as e:
raise LKGMNotCommitted('Could not submit LKGM: %r' % e)
logging.info('git cl land succeeded.')
def _GetParser():
"""Returns the parser to use for this module."""
parser = commandline.ArgumentParser(usage=__doc__, caching=True)
parser.add_argument('--dryrun', action='store_true', default=False,
help="Find the next LKGM but don't commit it.")
parser.add_argument('--lkgm', required=True,
help="LKGM version to update to.")
parser.add_argument('--user_email', required=False,
default='chromeos-commit-bot@chromium.org',
help="Email address to use when comitting changes.")
parser.add_argument('--workdir',
default=os.path.join(os.getcwd(), 'chrome_src'),
help=('Path to a checkout of the chrome src. '
'Defaults to PWD/chrome_src'))
return parser
def main(argv):
parser = _GetParser()
args = parser.parse_args(argv)
logging.info('lkgm=%s', args.lkgm)
logging.info('user_email=%s', args.user_email)
logging.info('workdir=%s', args.workdir)
committer = ChromeLGTMCommitter(args.workdir, lkgm=args.lkgm,
dryrun=args.dryrun,
user_email=args.user_email)
try:
committer.CheckoutChromeLKGM()
committer.CommitNewLKGM()
committer.UploadNewLKGM()
committer.LandNewLKGM()
except LKGMNotCommitted as e:
# TODO(stevenjb): Do not catch exceptions (i.e. fail) once this works.
logging.warning('LKGM Commit failed: %r' % e)
return 0