1 #!/usr/bin/python 2 3 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 4 # Use of this source code is governed by a BSD-style license that can be 5 # found in the LICENSE file. 6 7 8 """ 9 submit_try: Submit a try request. 10 11 This is a thin wrapper around the try request utilities in depot_tools which 12 adds some validation and supports both git and svn. 13 """ 14 15 16 import httplib 17 import json 18 import os 19 import subprocess 20 import sys 21 22 23 # Alias which can be used to run a try on every builder. 24 ALL_BUILDERS = 'all' 25 26 # Contact information for the build master. 27 # TODO(borenet): Share this information from a single location. Filed bug: 28 # http://code.google.com/p/skia/issues/detail?id=1081 29 SKIA_BUILD_MASTER_HOST = '70.32.156.51' 30 SKIA_BUILD_MASTER_PORT = '10117' 31 32 # All try builders have this suffix. 33 TRYBOT_SUFFIX = '_Trybot' 34 35 # Location of the codereview.settings file in the Skia repo. 36 SKIA_URL = 'skia.googlecode.com' 37 CODEREVIEW_SETTINGS = '/svn/codereview.settings' 38 39 # String for matching the svn url of the try server inside codereview.settings. 40 TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: ' 41 42 # Strings used for matching svn config properties. 43 URL_STR = 'URL: ' 44 REPO_ROOT_STR = 'Repository Root: ' 45 46 47 def FindDepotTools(): 48 """ Find depot_tools on the local machine and return its location. """ 49 which_cmd = 'where' if os.name == 'nt' else 'which' 50 cmd = [which_cmd, 'gcl'] 51 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 52 if proc.wait() != 0: 53 raise Exception('Couldn\'t find depot_tools in PATH!') 54 gcl = proc.communicate()[0].split('\n')[0].rstrip() 55 depot_tools_dir = os.path.dirname(gcl) 56 return depot_tools_dir 57 58 59 def GetCheckoutRoot(is_svn=True): 60 """ Determine where the local checkout is rooted. 61 62 is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in 63 a git checkout. 64 """ 65 if is_svn: 66 svn_cmd = 'svn.bat' if os.name == 'nt' else 'svn' 67 cmd = [svn_cmd, 'info'] 68 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 69 stderr=subprocess.STDOUT) 70 if proc.wait() != 0: 71 raise Exception('Couldn\'t find checkout root!') 72 output = proc.communicate()[0].split('\n') 73 url = None 74 repo_root = None 75 for line in output: 76 if line.startswith(REPO_ROOT_STR): 77 repo_root = line[len(REPO_ROOT_STR):].rstrip() 78 elif line.startswith(URL_STR): 79 url = line[len(URL_STR):].rstrip() 80 if not url or not repo_root: 81 raise Exception('Couldn\'t find checkout root!') 82 if url == repo_root: 83 return 'svn' 84 return url[len(repo_root)+1:] 85 else: 86 cmd = ['git', 'rev-parse', '--show-toplevel'] 87 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 88 stderr=subprocess.STDOUT) 89 if proc.wait() != 0: 90 raise Exception('Couldn\'t find checkout root!') 91 return os.path.basename(proc.communicate()[0]) 92 93 94 def GetTryRepo(): 95 """ Determine the TRYSERVER_SVN_URL from the codereview.settings file in the 96 Skia repo. """ 97 connection = httplib.HTTPConnection(SKIA_URL) 98 connection.request('GET', CODEREVIEW_SETTINGS) 99 content = connection.getresponse().read() 100 for line in content.split('\n'): 101 if line.startswith(TRYSERVER_SVN_URL): 102 return line[len(TRYSERVER_SVN_URL):].rstrip() 103 raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is ' 104 'defined in the %s file.' % CODEREVIEW_SETTINGS) 105 106 107 def RetrieveTrybotList(): 108 """ Retrieve the list of known trybots from the build master, stripping 109 TRYBOT_SUFFIX from the name. """ 110 trybots = [] 111 connection = httplib.HTTPConnection(SKIA_BUILD_MASTER_HOST, 112 SKIA_BUILD_MASTER_PORT) 113 connection.request('GET', '/json/builders') 114 response = connection.getresponse() 115 builders = json.load(response) 116 117 for builder in builders: 118 if builder.endswith(TRYBOT_SUFFIX): 119 trybots.append(builder[:-len(TRYBOT_SUFFIX)]) 120 return trybots 121 122 123 def ValidateArgs(argv, trybots, is_svn=True): 124 """ Parse and validate command-line arguments. If the arguments are valid, 125 returns a tuple of (<changelist name>, <list of trybots>). 126 127 trybots: A list of the known try builders. 128 """ 129 130 class CollectedArgs(object): 131 def __init__(self, bots, changelist, revision): 132 self._bots = bots 133 self._changelist = changelist 134 self._revision = revision 135 136 @property 137 def bots(self): 138 for bot in self._bots: 139 yield bot 140 141 @property 142 def changelist(self): 143 return self._changelist 144 145 @property 146 def revision(self): 147 return self._revision 148 149 usage = ( 150 """submit_try: Submit a try request. 151 submit_try %s--bot <buildername> [<buildername> ...] 152 153 --bot Builder on which to run the try. Required. 154 -h, --help Show this message. 155 -r <revision#> Revision from which to run the try. 156 -l, --list_bots List the available try builders and exit. 157 """ % ('<changelist> ' if is_svn else '')) 158 159 def Error(msg=None): 160 if msg: 161 print msg 162 print usage 163 sys.exit(1) 164 165 using_bots = None 166 changelist = None 167 revision = None 168 169 while argv: 170 arg = argv.pop(0) 171 if arg == '-h' or arg == '--help': 172 Error() 173 elif arg == '-l' or arg == '--list_bots': 174 print 'submit_try: Available builders:\n %s' % '\n '.join(trybots) 175 sys.exit(0) 176 elif arg == '--bot': 177 if using_bots: 178 Error('--bot specified multiple times.') 179 if len(argv) < 1: 180 Error('You must specify a builder with "--bot".') 181 using_bots = [] 182 while argv and not argv[0].startswith('-'): 183 bot = argv.pop(0) 184 if bot == ALL_BUILDERS: 185 if using_bots: 186 Error('Cannot specify "all" with additional builder names.') 187 using_bots = trybots 188 break 189 else: 190 if not bot in trybots: 191 Error('Unrecognized builder: %s' % bot) 192 using_bots.append(bot) 193 elif arg == '-r': 194 if len(argv) < 1: 195 Error('You must specify a revision with "-r".') 196 revision = argv.pop(0) 197 else: 198 if changelist or not is_svn: 199 Error('Unknown argument: %s' % arg) 200 changelist = arg 201 if is_svn and not changelist: 202 Error('You must specify a changelist name.') 203 if not using_bots: 204 Error('You must specify one or more builders using --bot.') 205 return CollectedArgs(bots=using_bots, changelist=changelist, 206 revision=revision) 207 208 209 def SubmitTryRequest(args, is_svn=True): 210 """ Submits a try request for the given changelist on the given list of 211 trybots. 212 213 args: Object whose properties are derived from command-line arguments. If 214 is_svn is True, it should contain: 215 - changelist: string; the name of the changelist to try. 216 - bot: list of strings; the names of the try builders to run. 217 - revision: optional, int; the revision number from which to run the try. 218 If is_svn is False, it should contain: 219 - bot: list of strings; the names of the try builders to run. 220 - revision: optional, int; the revision number from which to run the try. 221 is_svn: boolean; are we in an SVN repo? 222 """ 223 botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots]) 224 if is_svn: 225 gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl' 226 try_args = [gcl_cmd, 'try', args.changelist, 227 '--root', GetCheckoutRoot(is_svn), 228 '--bot', botlist] 229 if args.revision: 230 try_args.extend(['-r', args.revision]) 231 print ' '.join(try_args) 232 proc = subprocess.Popen(try_args, stdout=subprocess.PIPE, 233 stderr=subprocess.STDOUT) 234 if proc.wait() != 0: 235 raise Exception('Failed to submit try request: %s' % ( 236 proc.communicate()[0])) 237 print proc.communicate()[0] 238 else: 239 # First, find depot_tools. This is needed to import trychange. 240 sys.path.append(FindDepotTools()) 241 import trychange 242 try_args = ['--use_svn', 243 '--svn_repo', GetTryRepo(), 244 '--root', GetCheckoutRoot(is_svn), 245 '--bot', botlist] 246 if args.revision: 247 try_args.extend(['-r', args.revision]) 248 trychange.TryChange(try_args, None, False) 249 250 251 def main(): 252 # Retrieve the list of active try builders from the build master. 253 trybots = RetrieveTrybotList() 254 255 # Determine if we're in an SVN checkout. 256 is_svn = os.path.isdir('.svn') 257 258 # Parse and validate the command-line arguments. 259 args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn) 260 261 # Submit the try request. 262 SubmitTryRequest(args, is_svn=is_svn) 263 264 265 if __name__ == '__main__': 266 sys.exit(main())