#!/usr/bin/python # Copyright (c) 2013 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. """ submit_try: Submit a try request. This is a thin wrapper around the try request utilities in depot_tools which adds some validation and supports both git and svn. """ import httplib import json import os import subprocess import sys # Alias which can be used to run a try on every builder. ALL_BUILDERS = 'all' # Contact information for the build master. # TODO(borenet): Share this information from a single location. Filed bug: # http://code.google.com/p/skia/issues/detail?id=1081 SKIA_BUILD_MASTER_HOST = '70.32.156.51' SKIA_BUILD_MASTER_PORT = '10117' # All try builders have this suffix. TRYBOT_SUFFIX = '_Trybot' # Location of the codereview.settings file in the Skia repo. SKIA_URL = 'skia.googlecode.com' CODEREVIEW_SETTINGS = '/svn/codereview.settings' # String for matching the svn url of the try server inside codereview.settings. TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: ' # Strings used for matching svn config properties. URL_STR = 'URL: ' REPO_ROOT_STR = 'Repository Root: ' def FindDepotTools(): """ Find depot_tools on the local machine and return its location. """ which_cmd = 'where' if os.name == 'nt' else 'which' cmd = [which_cmd, 'gcl'] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.wait() != 0: raise Exception('Couldn\'t find depot_tools in PATH!') gcl = proc.communicate()[0].split('\n')[0].rstrip() depot_tools_dir = os.path.dirname(gcl) return depot_tools_dir def GetCheckoutRoot(is_svn=True): """ Determine where the local checkout is rooted. is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in a git checkout. """ if is_svn: svn_cmd = 'svn.bat' if os.name == 'nt' else 'svn' cmd = [svn_cmd, 'info'] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.wait() != 0: raise Exception('Couldn\'t find checkout root!') output = proc.communicate()[0].split('\n') url = None repo_root = None for line in output: if line.startswith(REPO_ROOT_STR): repo_root = line[len(REPO_ROOT_STR):].rstrip() elif line.startswith(URL_STR): url = line[len(URL_STR):].rstrip() if not url or not repo_root: raise Exception('Couldn\'t find checkout root!') if url == repo_root: return 'svn' return url[len(repo_root)+1:] else: cmd = ['git', 'rev-parse', '--show-toplevel'] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.wait() != 0: raise Exception('Couldn\'t find checkout root!') return os.path.basename(proc.communicate()[0]) def GetTryRepo(): """ Determine the TRYSERVER_SVN_URL from the codereview.settings file in the Skia repo. """ connection = httplib.HTTPConnection(SKIA_URL) connection.request('GET', CODEREVIEW_SETTINGS) content = connection.getresponse().read() for line in content.split('\n'): if line.startswith(TRYSERVER_SVN_URL): return line[len(TRYSERVER_SVN_URL):].rstrip() raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is ' 'defined in the %s file.' % CODEREVIEW_SETTINGS) def RetrieveTrybotList(): """ Retrieve the list of known trybots from the build master, stripping TRYBOT_SUFFIX from the name. """ trybots = [] connection = httplib.HTTPConnection(SKIA_BUILD_MASTER_HOST, SKIA_BUILD_MASTER_PORT) connection.request('GET', '/json/builders') response = connection.getresponse() builders = json.load(response) for builder in builders: if builder.endswith(TRYBOT_SUFFIX): trybots.append(builder[:-len(TRYBOT_SUFFIX)]) return trybots def ValidateArgs(argv, trybots, is_svn=True): """ Parse and validate command-line arguments. If the arguments are valid, returns a tuple of (<changelist name>, <list of trybots>). trybots: A list of the known try builders. """ class CollectedArgs(object): def __init__(self, bots, changelist, revision): self._bots = bots self._changelist = changelist self._revision = revision @property def bots(self): for bot in self._bots: yield bot @property def changelist(self): return self._changelist @property def revision(self): return self._revision usage = ( """submit_try: Submit a try request. submit_try %s--bot <buildername> [<buildername> ...] --bot Builder on which to run the try. Required. -h, --help Show this message. -r <revision#> Revision from which to run the try. -l, --list_bots List the available try builders and exit. """ % ('<changelist> ' if is_svn else '')) def Error(msg=None): if msg: print msg print usage sys.exit(1) using_bots = None changelist = None revision = None while argv: arg = argv.pop(0) if arg == '-h' or arg == '--help': Error() elif arg == '-l' or arg == '--list_bots': print 'submit_try: Available builders:\n %s' % '\n '.join(trybots) sys.exit(0) elif arg == '--bot': if using_bots: Error('--bot specified multiple times.') if len(argv) < 1: Error('You must specify a builder with "--bot".') using_bots = [] while argv and not argv[0].startswith('-'): bot = argv.pop(0) if bot == ALL_BUILDERS: if using_bots: Error('Cannot specify "all" with additional builder names.') using_bots = trybots break else: if not bot in trybots: Error('Unrecognized builder: %s' % bot) using_bots.append(bot) elif arg == '-r': if len(argv) < 1: Error('You must specify a revision with "-r".') revision = argv.pop(0) else: if changelist or not is_svn: Error('Unknown argument: %s' % arg) changelist = arg if is_svn and not changelist: Error('You must specify a changelist name.') if not using_bots: Error('You must specify one or more builders using --bot.') return CollectedArgs(bots=using_bots, changelist=changelist, revision=revision) def SubmitTryRequest(args, is_svn=True): """ Submits a try request for the given changelist on the given list of trybots. args: Object whose properties are derived from command-line arguments. If is_svn is True, it should contain: - changelist: string; the name of the changelist to try. - bot: list of strings; the names of the try builders to run. - revision: optional, int; the revision number from which to run the try. If is_svn is False, it should contain: - bot: list of strings; the names of the try builders to run. - revision: optional, int; the revision number from which to run the try. is_svn: boolean; are we in an SVN repo? """ botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots]) if is_svn: gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl' try_args = [gcl_cmd, 'try', args.changelist, '--root', GetCheckoutRoot(is_svn), '--bot', botlist] if args.revision: try_args.extend(['-r', args.revision]) print ' '.join(try_args) proc = subprocess.Popen(try_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.wait() != 0: raise Exception('Failed to submit try request: %s' % ( proc.communicate()[0])) print proc.communicate()[0] else: # First, find depot_tools. This is needed to import trychange. sys.path.append(FindDepotTools()) import trychange try_args = ['--use_svn', '--svn_repo', GetTryRepo(), '--root', GetCheckoutRoot(is_svn), '--bot', botlist] if args.revision: try_args.extend(['-r', args.revision]) trychange.TryChange(try_args, None, False) def main(): # Retrieve the list of active try builders from the build master. trybots = RetrieveTrybotList() # Determine if we're in an SVN checkout. is_svn = os.path.isdir('.svn') # Parse and validate the command-line arguments. args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn) # Submit the try request. SubmitTryRequest(args, is_svn=is_svn) if __name__ == '__main__': sys.exit(main())