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 25 GLOBAL_VARIABLES = json.loads(svn.Cat('http://skia.googlecode.com/svn/' 26 'buildbot/site_config/' 27 'global_variables.json')) 28 29 30 def GetGlobalVariable(var_name): 31 return GLOBAL_VARIABLES[var_name]['value'] 32 33 34 # Alias which can be used to run a try on every builder. 35 ALL_BUILDERS = 'all' 36 # Alias which can be used to run a try on all compile builders. 37 COMPILE_BUILDERS = 'compile' 38 # Alias which can be used to run a try on all builders that are run in the CQ. 39 CQ_BUILDERS = 'cq' 40 # Alias which can be used to specify a regex to choose builders. 41 REGEX = 'regex' 42 43 ALL_ALIASES = [ALL_BUILDERS, COMPILE_BUILDERS, CQ_BUILDERS, REGEX] 44 45 # Contact information for the build master. 46 SKIA_BUILD_MASTER_HOST = str(GetGlobalVariable('master_host')) 47 SKIA_BUILD_MASTER_PORT = str(GetGlobalVariable('external_port')) 48 49 # All try builders have this suffix. 50 TRYBOT_SUFFIX = '-Trybot' 51 52 # Location of the codereview.settings file in the Skia repo. 53 SKIA_URL = 'skia.googlecode.com' 54 CODEREVIEW_SETTINGS = '/svn/codereview.settings' 55 56 # String for matching the svn url of the try server inside codereview.settings. 57 TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: ' 58 59 # Strings used for matching svn config properties. 60 URL_STR = 'URL' 61 REPO_ROOT_STR = 'Repository Root' 62 63 64 def FindDepotTools(): 65 """ Find depot_tools on the local machine and return its location. """ 66 which_cmd = 'where' if os.name == 'nt' else 'which' 67 cmd = [which_cmd, 'gcl'] 68 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 69 if proc.wait() != 0: 70 raise Exception('Couldn\'t find depot_tools in PATH!') 71 gcl = proc.communicate()[0].split('\n')[0].rstrip() 72 depot_tools_dir = os.path.dirname(gcl) 73 return depot_tools_dir 74 75 76 def GetCheckoutRoot(is_svn=True): 77 """ Determine where the local checkout is rooted. 78 79 is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in 80 a git checkout. 81 """ 82 if is_svn: 83 repo = svn.Svn(os.curdir) 84 svn_info = repo.GetInfo() 85 url = svn_info.get(URL_STR, None) 86 repo_root = svn_info.get(REPO_ROOT_STR, None) 87 if not url or not repo_root: 88 raise Exception('Couldn\'t find checkout root!') 89 if url == repo_root: 90 return 'svn' 91 return url[len(repo_root)+1:] 92 else: 93 cmd = ['git', 'rev-parse', '--show-toplevel'] 94 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 95 stderr=subprocess.STDOUT) 96 if proc.wait() != 0: 97 raise Exception('Couldn\'t find checkout root!') 98 return os.path.basename(proc.communicate()[0]) 99 100 101 def GetTryRepo(): 102 """ Determine the TRYSERVER_SVN_URL from the codereview.settings file in the 103 Skia repo. """ 104 connection = httplib.HTTPConnection(SKIA_URL) 105 connection.request('GET', CODEREVIEW_SETTINGS) 106 content = connection.getresponse().read() 107 for line in content.split('\n'): 108 if line.startswith(TRYSERVER_SVN_URL): 109 return line[len(TRYSERVER_SVN_URL):].rstrip() 110 raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is ' 111 'defined in the %s file.' % CODEREVIEW_SETTINGS) 112 113 114 def RetrieveTrybotList(json_filename): 115 """ Retrieve the list of known trybots from the build master, stripping 116 TRYBOT_SUFFIX from the name. """ 117 trybots = [] 118 connection = httplib.HTTPConnection(SKIA_BUILD_MASTER_HOST, 119 SKIA_BUILD_MASTER_PORT) 120 connection.request('GET', '/json/%s' % json_filename) 121 response = connection.getresponse() 122 builders = json.load(response) 123 124 for builder in builders: 125 if builder.endswith(TRYBOT_SUFFIX): 126 trybots.append(builder[:-len(TRYBOT_SUFFIX)]) 127 return trybots 128 129 130 def ValidateArgs(argv, trybots, is_svn=True): 131 """ Parse and validate command-line arguments. If the arguments are valid, 132 returns a tuple of (<changelist name>, <list of trybots>). 133 134 trybots: A list of the known try builders. 135 """ 136 137 class CollectedArgs(object): 138 def __init__(self, bots, changelist, revision): 139 self._bots = bots 140 self._changelist = changelist 141 self._revision = revision 142 143 @property 144 def bots(self): 145 for bot in self._bots: 146 yield bot 147 148 @property 149 def changelist(self): 150 return self._changelist 151 152 @property 153 def revision(self): 154 return self._revision 155 156 usage = ( 157 """submit_try: Submit a try request. 158 submit_try %s--bot <buildername> [<buildername> ...] 159 160 -b, --bot Builder(s) or Alias on which to run the try. Required. 161 Allowed aliases: %s 162 -h, --help Show this message. 163 -r <revision#> Revision from which to run the try. 164 -l, --list_bots List the available try builders and aliases and exit. 165 """ % ('<changelist> ' if is_svn else '', ALL_ALIASES)) 166 167 def Error(msg=None): 168 if msg: 169 print msg 170 print usage 171 sys.exit(1) 172 173 using_bots = None 174 changelist = None 175 revision = None 176 177 while argv: 178 arg = argv.pop(0) 179 if arg == '-h' or arg == '--help': 180 Error() 181 elif arg == '-l' or arg == '--list_bots': 182 format_args = ['\n '.join(trybots)] + ALL_ALIASES 183 print ( 184 """ 185 submit_try: Available builders:\n %s 186 187 Can also use the following aliases to run on groups of builders- 188 %s: Will run against all trybots. 189 %s: Will run against all compile trybots. 190 %s: Will run against the same trybots as the commit queue. 191 %s: You will be prompted to enter a regex to select builders with. 192 193 """ % tuple(format_args)) 194 sys.exit(0) 195 elif arg == '-b' or arg == '--bot': 196 if using_bots: 197 Error('--bot specified multiple times.') 198 if len(argv) < 1: 199 Error('You must specify a builder with "--bot".') 200 using_bots = [] 201 while argv and not argv[0].startswith('-'): 202 for bot in argv.pop(0).split(','): 203 if bot in ALL_ALIASES: 204 if using_bots: 205 Error('Cannot specify "%s" with additional builder names or ' 206 'aliases.' % bot) 207 if bot == ALL_BUILDERS: 208 are_you_sure = raw_input('Running a try on every bot is very ' 209 'expensive. You may be able to get ' 210 'enough information by running on a ' 211 'smaller set of bots. Are you sure you ' 212 'want to run your try job on all of the ' 213 'trybots? [y,n]: ') 214 if are_you_sure == 'y': 215 using_bots = trybots 216 elif bot == COMPILE_BUILDERS: 217 using_bots = [t for t in trybots if t.startswith('Build')] 218 elif bot == CQ_BUILDERS: 219 using_bots = RetrieveTrybotList(json_filename='cqtrybots') 220 elif bot == REGEX: 221 while True: 222 regex = raw_input("Enter your trybot regex: ") 223 p = re.compile(regex) 224 using_bots = [t for t in trybots if p.match(t)] 225 print '\n\nTrybots that match your regex:\n%s\n\n' % '\n'.join( 226 using_bots) 227 if raw_input('Re-enter regex? [y,n]: ') == 'n': 228 break 229 break 230 else: 231 if not bot in trybots: 232 Error('Unrecognized builder: %s' % bot) 233 using_bots.append(bot) 234 elif arg == '-r': 235 if len(argv) < 1: 236 Error('You must specify a revision with "-r".') 237 revision = argv.pop(0) 238 else: 239 if changelist or not is_svn: 240 Error('Unknown argument: %s' % arg) 241 changelist = arg 242 if is_svn and not changelist: 243 Error('You must specify a changelist name.') 244 if not using_bots: 245 Error('You must specify one or more builders using --bot.') 246 return CollectedArgs(bots=using_bots, changelist=changelist, 247 revision=revision) 248 249 250 def SubmitTryRequest(args, is_svn=True): 251 """ Submits a try request for the given changelist on the given list of 252 trybots. 253 254 args: Object whose properties are derived from command-line arguments. If 255 is_svn is True, it should contain: 256 - changelist: string; the name of the changelist to try. 257 - bot: list of strings; the names of the try builders to run. 258 - revision: optional, int; the revision number from which to run the try. 259 If is_svn is False, it should contain: 260 - bot: list of strings; the names of the try builders to run. 261 - revision: optional, int; the revision number from which to run the try. 262 is_svn: boolean; are we in an SVN repo? 263 """ 264 botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots]) 265 if is_svn: 266 gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl' 267 try_args = [gcl_cmd, 'try', args.changelist, 268 '--root', GetCheckoutRoot(is_svn), 269 '--bot', botlist] 270 if args.revision: 271 try_args.extend(['-r', args.revision]) 272 print ' '.join(try_args) 273 proc = subprocess.Popen(try_args, stdout=subprocess.PIPE, 274 stderr=subprocess.STDOUT) 275 if proc.wait() != 0: 276 raise Exception('Failed to submit try request: %s' % ( 277 proc.communicate()[0])) 278 print proc.communicate()[0] 279 else: 280 # First, find depot_tools. This is needed to import trychange. 281 sys.path.append(FindDepotTools()) 282 import trychange 283 try_args = ['--use_svn', 284 '--svn_repo', GetTryRepo(), 285 '--root', GetCheckoutRoot(is_svn), 286 '--bot', botlist] 287 if args.revision: 288 try_args.extend(['-r', args.revision]) 289 trychange.TryChange(try_args, None, False) 290 291 292 def main(): 293 # Retrieve the list of active try builders from the build master. 294 trybots = RetrieveTrybotList(json_filename='trybots') 295 296 # Determine if we're in an SVN checkout. 297 is_svn = os.path.isdir('.svn') 298 299 # Parse and validate the command-line arguments. 300 args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn) 301 302 # Submit the try request. 303 SubmitTryRequest(args, is_svn=is_svn) 304 305 306 if __name__ == '__main__': 307 sys.exit(main()) 308