blob: 6647b89e1c1a8593de73314bbe958d156ffb87da [file] [log] [blame]
borenet@google.com6b5388e2013-01-23 20:54:29 +00001#!/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"""
9submit_try: Submit a try request.
10
11This is a thin wrapper around the try request utilities in depot_tools which
12adds some validation and supports both git and svn.
13"""
14
15
borenet@google.com6b5388e2013-01-23 20:54:29 +000016import httplib
17import json
18import os
rmistry@google.comf5c4fc82013-04-09 11:46:46 +000019import re
borenet@google.comd4ba6e72014-01-08 21:00:30 +000020import shutil
borenet@google.com6b5388e2013-01-23 20:54:29 +000021import subprocess
borenet@google.coma74302d2013-03-18 18:18:26 +000022import svn
borenet@google.com6b5388e2013-01-23 20:54:29 +000023import sys
borenet@google.comd4ba6e72014-01-08 21:00:30 +000024import tempfile
borenet@google.comfe7533e2013-03-11 20:09:40 +000025
epoger@google.comfd040112013-08-20 16:21:55 +000026import buildbot_globals
borenet@google.com6b5388e2013-01-23 20:54:29 +000027
28
29# Alias which can be used to run a try on every builder.
30ALL_BUILDERS = 'all'
rmistry@google.comf5c4fc82013-04-09 11:46:46 +000031# Alias which can be used to run a try on all compile builders.
32COMPILE_BUILDERS = 'compile'
33# Alias which can be used to run a try on all builders that are run in the CQ.
34CQ_BUILDERS = 'cq'
35# Alias which can be used to specify a regex to choose builders.
36REGEX = 'regex'
37
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +000038ALL_ALIASES = [ALL_BUILDERS, COMPILE_BUILDERS, REGEX, CQ_BUILDERS]
borenet@google.com6b5388e2013-01-23 20:54:29 +000039
borenet@google.com1a5e83a2014-01-14 18:03:10 +000040GIT = 'git.bat' if os.name == 'nt' else 'git'
41
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +000042# URL of the slaves.cfg file in the Skia buildbot sources.
43SLAVES_CFG_URL = ('https://skia.googlesource.com/buildbot/+/master/'
44 'master/slaves.cfg')
borenet@google.com6b5388e2013-01-23 20:54:29 +000045
46# All try builders have this suffix.
borenet@google.com99da6012013-05-02 12:14:35 +000047TRYBOT_SUFFIX = '-Trybot'
borenet@google.com6b5388e2013-01-23 20:54:29 +000048
borenet@google.com6b5388e2013-01-23 20:54:29 +000049# String for matching the svn url of the try server inside codereview.settings.
50TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: '
51
52# Strings used for matching svn config properties.
borenet@google.coma74302d2013-03-18 18:18:26 +000053URL_STR = 'URL'
54REPO_ROOT_STR = 'Repository Root'
borenet@google.com6b5388e2013-01-23 20:54:29 +000055
56
57def 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!')
borenet@google.com6c55b512013-01-24 21:38:51 +000064 gcl = proc.communicate()[0].split('\n')[0].rstrip()
borenet@google.com6b5388e2013-01-23 20:54:29 +000065 depot_tools_dir = os.path.dirname(gcl)
66 return depot_tools_dir
67
68
69def 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:
borenet@google.coma74302d2013-03-18 18:18:26 +000076 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)
borenet@google.com6b5388e2013-01-23 20:54:29 +000080 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
94def GetTryRepo():
borenet@google.com6f0f5b42014-01-09 21:41:39 +000095 """Determine the TRYSERVER_SVN_URL from the codereview.settings file."""
96 codereview_settings_file = os.path.join(os.path.dirname(__file__), os.pardir,
97 'codereview.settings')
98 with open(codereview_settings_file) as f:
99 for line in f:
100 if line.startswith(TRYSERVER_SVN_URL):
101 return line[len(TRYSERVER_SVN_URL):].rstrip()
borenet@google.com6b5388e2013-01-23 20:54:29 +0000102 raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is '
borenet@google.com6f0f5b42014-01-09 21:41:39 +0000103 'defined in the %s file.' % codereview_settings_file)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000104
105
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000106def RetrieveTrybotList():
107 """Retrieve the list of known trybots from the checked-in buildbot
108 configuration."""
109 # Retrieve the slaves.cfg file from the repository.
110 slaves_cfg_text = buildbot_globals.retrieve_from_googlesource(SLAVES_CFG_URL)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000111
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000112 # Execute the slaves.cfg file to obtain the list of slaves.
113 vars = {}
114 exec(slaves_cfg_text, vars)
115 slaves_cfg = vars['slaves']
116
117 # Pull the list of known builders from the slaves list.
118 trybots = set()
119 for slave in slaves_cfg:
120 for builder in slave['builder']:
121 if not builder.endswith(TRYBOT_SUFFIX):
122 trybots.add(builder)
123
124 return list(trybots), vars['cq_trybots']
borenet@google.com6b5388e2013-01-23 20:54:29 +0000125
126
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000127def ValidateArgs(argv, trybots, cq_trybots, is_svn=True):
borenet@google.com6b5388e2013-01-23 20:54:29 +0000128 """ Parse and validate command-line arguments. If the arguments are valid,
129 returns a tuple of (<changelist name>, <list of trybots>).
130
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000131 trybots: list of strings; A list of the known try builders.
132 cq_trybots: list of strings; Trybots who get run by the commit queue.
133 is_svn: bool; whether or not we're in an svn checkout.
borenet@google.com6b5388e2013-01-23 20:54:29 +0000134 """
borenet@google.com6b5388e2013-01-23 20:54:29 +0000135
borenet@google.coma5d621f2013-01-25 20:55:35 +0000136 class CollectedArgs(object):
137 def __init__(self, bots, changelist, revision):
138 self._bots = bots
139 self._changelist = changelist
140 self._revision = revision
141
142 @property
143 def bots(self):
144 for bot in self._bots:
145 yield bot
146
147 @property
148 def changelist(self):
149 return self._changelist
150
151 @property
152 def revision(self):
153 return self._revision
154
155 usage = (
156"""submit_try: Submit a try request.
157submit_try %s--bot <buildername> [<buildername> ...]
158
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000159-b, --bot Builder(s) or Alias on which to run the try. Required.
160 Allowed aliases: %s
borenet@google.coma5d621f2013-01-25 20:55:35 +0000161-h, --help Show this message.
162-r <revision#> Revision from which to run the try.
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000163-l, --list_bots List the available try builders and aliases and exit.
164""" % ('<changelist> ' if is_svn else '', ALL_ALIASES))
borenet@google.coma5d621f2013-01-25 20:55:35 +0000165
166 def Error(msg=None):
167 if msg:
168 print msg
169 print usage
170 sys.exit(1)
171
172 using_bots = None
173 changelist = None
174 revision = None
175
176 while argv:
177 arg = argv.pop(0)
178 if arg == '-h' or arg == '--help':
179 Error()
180 elif arg == '-l' or arg == '--list_bots':
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000181 format_args = ['\n '.join(sorted(trybots))] + \
182 ALL_ALIASES + \
183 ['\n '.join(sorted(cq_trybots))]
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000184 print (
185"""
186submit_try: Available builders:\n %s
187
188Can also use the following aliases to run on groups of builders-
189 %s: Will run against all trybots.
190 %s: Will run against all compile trybots.
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000191 %s: You will be prompted to enter a regex to select builders with.
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000192 %s: Will run against the same trybots as the commit queue:\n %s
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000193
194""" % tuple(format_args))
borenet@google.coma5d621f2013-01-25 20:55:35 +0000195 sys.exit(0)
borenet@google.com02009c72013-04-01 17:59:16 +0000196 elif arg == '-b' or arg == '--bot':
borenet@google.coma5d621f2013-01-25 20:55:35 +0000197 if using_bots:
198 Error('--bot specified multiple times.')
199 if len(argv) < 1:
200 Error('You must specify a builder with "--bot".')
201 using_bots = []
202 while argv and not argv[0].startswith('-'):
epoger@google.com50d68622013-04-03 18:35:35 +0000203 for bot in argv.pop(0).split(','):
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000204 if bot in ALL_ALIASES:
epoger@google.com50d68622013-04-03 18:35:35 +0000205 if using_bots:
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000206 Error('Cannot specify "%s" with additional builder names or '
207 'aliases.' % bot)
208 if bot == ALL_BUILDERS:
borenet@google.com6fb77562013-07-12 18:11:04 +0000209 are_you_sure = raw_input('Running a try on every bot is very '
210 'expensive. You may be able to get '
211 'enough information by running on a '
212 'smaller set of bots. Are you sure you '
213 'want to run your try job on all of the '
214 'trybots? [y,n]: ')
215 if are_you_sure == 'y':
216 using_bots = trybots
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000217 elif bot == COMPILE_BUILDERS:
borenet@google.com6fb77562013-07-12 18:11:04 +0000218 using_bots = [t for t in trybots if t.startswith('Build')]
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000219 elif bot == CQ_BUILDERS:
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000220 using_bots = cq_trybots
rmistry@google.comf5c4fc82013-04-09 11:46:46 +0000221 elif bot == REGEX:
222 while True:
223 regex = raw_input("Enter your trybot regex: ")
224 p = re.compile(regex)
225 using_bots = [t for t in trybots if p.match(t)]
226 print '\n\nTrybots that match your regex:\n%s\n\n' % '\n'.join(
227 using_bots)
228 if raw_input('Re-enter regex? [y,n]: ') == 'n':
229 break
epoger@google.com50d68622013-04-03 18:35:35 +0000230 break
231 else:
232 if not bot in trybots:
233 Error('Unrecognized builder: %s' % bot)
234 using_bots.append(bot)
borenet@google.coma5d621f2013-01-25 20:55:35 +0000235 elif arg == '-r':
236 if len(argv) < 1:
237 Error('You must specify a revision with "-r".')
238 revision = argv.pop(0)
239 else:
240 if changelist or not is_svn:
241 Error('Unknown argument: %s' % arg)
242 changelist = arg
243 if is_svn and not changelist:
244 Error('You must specify a changelist name.')
245 if not using_bots:
246 Error('You must specify one or more builders using --bot.')
247 return CollectedArgs(bots=using_bots, changelist=changelist,
248 revision=revision)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000249
250
251def SubmitTryRequest(args, is_svn=True):
252 """ Submits a try request for the given changelist on the given list of
253 trybots.
254
255 args: Object whose properties are derived from command-line arguments. If
256 is_svn is True, it should contain:
257 - changelist: string; the name of the changelist to try.
258 - bot: list of strings; the names of the try builders to run.
259 - revision: optional, int; the revision number from which to run the try.
260 If is_svn is False, it should contain:
261 - bot: list of strings; the names of the try builders to run.
262 - revision: optional, int; the revision number from which to run the try.
263 is_svn: boolean; are we in an SVN repo?
264 """
borenet@google.coma5d621f2013-01-25 20:55:35 +0000265 botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots])
borenet@google.com6b5388e2013-01-23 20:54:29 +0000266 if is_svn:
borenet@google.com6c55b512013-01-24 21:38:51 +0000267 gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl'
268 try_args = [gcl_cmd, 'try', args.changelist,
269 '--root', GetCheckoutRoot(is_svn),
borenet@google.com6b5388e2013-01-23 20:54:29 +0000270 '--bot', botlist]
271 if args.revision:
272 try_args.extend(['-r', args.revision])
borenet@google.com6c55b512013-01-24 21:38:51 +0000273 print ' '.join(try_args)
274 proc = subprocess.Popen(try_args, stdout=subprocess.PIPE,
275 stderr=subprocess.STDOUT)
276 if proc.wait() != 0:
277 raise Exception('Failed to submit try request: %s' % (
278 proc.communicate()[0]))
279 print proc.communicate()[0]
borenet@google.com6b5388e2013-01-23 20:54:29 +0000280 else:
borenet@google.com1a5e83a2014-01-14 18:03:10 +0000281 # Find depot_tools. This is needed to import git_cl and trychange.
282 sys.path.append(FindDepotTools())
283 import git_cl
284 import trychange
285
286 cmd = [GIT, 'diff', git_cl.Changelist().GetUpstreamBranch(),
287 '--no-ext-diff']
borenet@google.comd4ba6e72014-01-08 21:00:30 +0000288 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
commit-bot@chromium.org240196d2014-01-16 19:48:19 +0000289 git_data = proc.communicate()
290 if git_data[0] is None:
borenet@google.comd4ba6e72014-01-08 21:00:30 +0000291 raise Exception('Failed to capture git diff!')
292
293 temp_dir = tempfile.mkdtemp()
294 try:
295 diff_file = os.path.join(temp_dir, 'patch.diff')
296 with open(diff_file, 'wb') as f:
commit-bot@chromium.org240196d2014-01-16 19:48:19 +0000297 f.write(git_data[0])
298 f.close()
299
borenet@google.comd4ba6e72014-01-08 21:00:30 +0000300 try_args = ['--use_svn',
301 '--svn_repo', GetTryRepo(),
302 '--root', GetCheckoutRoot(is_svn),
303 '--bot', botlist,
304 '--diff', diff_file,
305 ]
306 if args.revision:
307 try_args.extend(['-r', args.revision])
308
309 # Submit the try request.
310 trychange.TryChange(try_args, None, False)
311 finally:
312 shutil.rmtree(temp_dir)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000313
314
315def main():
316 # Retrieve the list of active try builders from the build master.
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000317 trybots, cq_trybots = RetrieveTrybotList()
borenet@google.com6b5388e2013-01-23 20:54:29 +0000318
319 # Determine if we're in an SVN checkout.
320 is_svn = os.path.isdir('.svn')
321
322 # Parse and validate the command-line arguments.
commit-bot@chromium.org9f8e2822014-01-14 19:18:45 +0000323 args = ValidateArgs(sys.argv[1:], trybots=trybots, cq_trybots=cq_trybots,
324 is_svn=is_svn)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000325
326 # Submit the try request.
327 SubmitTryRequest(args, is_svn=is_svn)
328
329
330if __name__ == '__main__':
epoger@google.com50d68622013-04-03 18:35:35 +0000331 sys.exit(main())