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 re 20 import subprocess 21 import svn 22 import sys 23 24 import buildbot_globals 25 26 27 # Alias which can be used to run a try on every builder. 28 ALL_BUILDERS = 'all' 29 # Alias which can be used to run a try on all compile builders. 30 COMPILE_BUILDERS = 'compile' 31 # Alias which can be used to run a try on all builders that are run in the CQ. 32 CQ_BUILDERS = 'cq' 33 # Alias which can be used to specify a regex to choose builders. 34 REGEX = 'regex' 35 36 ALL_ALIASES = [ALL_BUILDERS, COMPILE_BUILDERS, CQ_BUILDERS, REGEX] 37 38 # Contact information for the build master. 39 SKIA_BUILD_MASTER_HOST = str(buildbot_globals.Get('public_master_host')) 40 SKIA_BUILD_MASTER_PORT = str(buildbot_globals.Get('public_external_port')) 41 42 # All try builders have this suffix. 43 TRYBOT_SUFFIX = '-Trybot' 44 45 # Location of the codereview.settings file in the Skia repo. 46 SKIA_URL = 'skia.googlecode.com' 47 CODEREVIEW_SETTINGS = '/svn/codereview.settings' 48 49 # String for matching the svn url of the try server inside codereview.settings. 50 TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: ' 51 52 # Strings used for matching svn config properties. 53 URL_STR = 'URL' 54 REPO_ROOT_STR = 'Repository Root' 55 56 57 def FindDepotTools(): 58 """ Find depot_tools on the local machine and return its location. """ 59 which_cmd = 'where' if os.name == 'nt' else 'which' 60 cmd = [which_cmd, 'gcl'] 61 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 62 if proc.wait() != 0: 63 raise Exception('Couldn\'t find depot_tools in PATH!') 64 gcl = proc.communicate()[0].split('\n')[0].rstrip() 65 depot_tools_dir = os.path.dirname(gcl) 66 return depot_tools_dir 67 68 69 def GetCheckoutRoot(is_svn=True): 70 """ Determine where the local checkout is rooted. 71 72 is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in 73 a git checkout. 74 """ 75 if is_svn: 76 repo = svn.Svn(os.curdir) 77 svn_info = repo.GetInfo() 78 url = svn_info.get(URL_STR, None) 79 repo_root = svn_info.get(REPO_ROOT_STR, None) 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(json_filename): 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/%s' % json_filename) 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 -b, --bot Builder(s) or Alias on which to run the try. Required. 154 Allowed aliases: %s 155 -h, --help Show this message. 156 -r <revision#> Revision from which to run the try. 157 -l, --list_bots List the available try builders and aliases and exit. 158 """ % ('<changelist> ' if is_svn else '', ALL_ALIASES)) 159 160 def Error(msg=None): 161 if msg: 162 print msg 163 print usage 164 sys.exit(1) 165 166 using_bots = None 167 changelist = None 168 revision = None 169 170 while argv: 171 arg = argv.pop(0) 172 if arg == '-h' or arg == '--help': 173 Error() 174 elif arg == '-l' or arg == '--list_bots': 175 format_args = ['\n '.join(sorted(trybots))] + ALL_ALIASES 176 print ( 177 """ 178 submit_try: Available builders:\n %s 179 180 Can also use the following aliases to run on groups of builders- 181 %s: Will run against all trybots. 182 %s: Will run against all compile trybots. 183 %s: Will run against the same trybots as the commit queue. 184 %s: You will be prompted to enter a regex to select builders with. 185 186 """ % tuple(format_args)) 187 sys.exit(0) 188 elif arg == '-b' or arg == '--bot': 189 if using_bots: 190 Error('--bot specified multiple times.') 191 if len(argv) < 1: 192 Error('You must specify a builder with "--bot".') 193 using_bots = [] 194 while argv and not argv[0].startswith('-'): 195 for bot in argv.pop(0).split(','): 196 if bot in ALL_ALIASES: 197 if using_bots: 198 Error('Cannot specify "%s" with additional builder names or ' 199 'aliases.' % bot) 200 if bot == ALL_BUILDERS: 201 are_you_sure = raw_input('Running a try on every bot is very ' 202 'expensive. You may be able to get ' 203 'enough information by running on a ' 204 'smaller set of bots. Are you sure you ' 205 'want to run your try job on all of the ' 206 'trybots? [y,n]: ') 207 if are_you_sure == 'y': 208 using_bots = trybots 209 elif bot == COMPILE_BUILDERS: 210 using_bots = [t for t in trybots if t.startswith('Build')] 211 elif bot == CQ_BUILDERS: 212 using_bots = RetrieveTrybotList(json_filename='cqtrybots') 213 elif bot == REGEX: 214 while True: 215 regex = raw_input("Enter your trybot regex: ") 216 p = re.compile(regex) 217 using_bots = [t for t in trybots if p.match(t)] 218 print '\n\nTrybots that match your regex:\n%s\n\n' % '\n'.join( 219 using_bots) 220 if raw_input('Re-enter regex? [y,n]: ') == 'n': 221 break 222 break 223 else: 224 if not bot in trybots: 225 Error('Unrecognized builder: %s' % bot) 226 using_bots.append(bot) 227 elif arg == '-r': 228 if len(argv) < 1: 229 Error('You must specify a revision with "-r".') 230 revision = argv.pop(0) 231 else: 232 if changelist or not is_svn: 233 Error('Unknown argument: %s' % arg) 234 changelist = arg 235 if is_svn and not changelist: 236 Error('You must specify a changelist name.') 237 if not using_bots: 238 Error('You must specify one or more builders using --bot.') 239 return CollectedArgs(bots=using_bots, changelist=changelist, 240 revision=revision) 241 242 243 def SubmitTryRequest(args, is_svn=True): 244 """ Submits a try request for the given changelist on the given list of 245 trybots. 246 247 args: Object whose properties are derived from command-line arguments. If 248 is_svn is True, it should contain: 249 - changelist: string; the name of the changelist to try. 250 - bot: list of strings; the names of the try builders to run. 251 - revision: optional, int; the revision number from which to run the try. 252 If is_svn is False, it should contain: 253 - bot: list of strings; the names of the try builders to run. 254 - revision: optional, int; the revision number from which to run the try. 255 is_svn: boolean; are we in an SVN repo? 256 """ 257 botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots]) 258 if is_svn: 259 gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl' 260 try_args = [gcl_cmd, 'try', args.changelist, 261 '--root', GetCheckoutRoot(is_svn), 262 '--bot', botlist] 263 if args.revision: 264 try_args.extend(['-r', args.revision]) 265 print ' '.join(try_args) 266 proc = subprocess.Popen(try_args, stdout=subprocess.PIPE, 267 stderr=subprocess.STDOUT) 268 if proc.wait() != 0: 269 raise Exception('Failed to submit try request: %s' % ( 270 proc.communicate()[0])) 271 print proc.communicate()[0] 272 else: 273 # First, find depot_tools. This is needed to import trychange. 274 sys.path.append(FindDepotTools()) 275 import trychange 276 try_args = ['--use_svn', 277 '--svn_repo', GetTryRepo(), 278 '--root', GetCheckoutRoot(is_svn), 279 '--bot', botlist, 280 '--patchlevel', '0'] 281 if args.revision: 282 try_args.extend(['-r', args.revision]) 283 trychange.TryChange(try_args, None, False) 284 285 286 def main(): 287 # Retrieve the list of active try builders from the build master. 288 trybots = RetrieveTrybotList(json_filename='trybots') 289 290 # Determine if we're in an SVN checkout. 291 is_svn = os.path.isdir('.svn') 292 293 # Parse and validate the command-line arguments. 294 args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn) 295 296 # Submit the try request. 297 SubmitTryRequest(args, is_svn=is_svn) 298 299 300 if __name__ == '__main__': 301 sys.exit(main()) 302