blob: 8bc50332a8419b44301704626ea1f00bb192ad96 [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.comfe7533e2013-03-11 20:09:40 +000016from contextlib import closing
17
borenet@google.com6b5388e2013-01-23 20:54:29 +000018import httplib
19import json
20import os
21import subprocess
22import sys
borenet@google.comfe7533e2013-03-11 20:09:40 +000023import urllib2
24
25
26def GetGlobalVariables():
27 """ Retrieve a global variable from the global_variables.json file. """
28 global_variables_file = ('http://skia.googlecode.com/svn/buildbot/'
29 'site_config/global_variables.json')
30 with closing(urllib2.urlopen(global_variables_file)) as f:
31 return json.load(f)
32
33
34GLOBAL_VARIABLES = GetGlobalVariables()
35
36
37def GetGlobalVariable(var_name):
38 return GLOBAL_VARIABLES[var_name]['value']
borenet@google.com6b5388e2013-01-23 20:54:29 +000039
40
41# Alias which can be used to run a try on every builder.
42ALL_BUILDERS = 'all'
43
44# Contact information for the build master.
borenet@google.comfe7533e2013-03-11 20:09:40 +000045SKIA_BUILD_MASTER_HOST = str(GetGlobalVariable('master_host'))
46SKIA_BUILD_MASTER_PORT = str(GetGlobalVariable('external_port'))
borenet@google.com6b5388e2013-01-23 20:54:29 +000047
48# All try builders have this suffix.
49TRYBOT_SUFFIX = '_Trybot'
50
51# Location of the codereview.settings file in the Skia repo.
52SKIA_URL = 'skia.googlecode.com'
53CODEREVIEW_SETTINGS = '/svn/codereview.settings'
54
55# String for matching the svn url of the try server inside codereview.settings.
56TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: '
57
58# Strings used for matching svn config properties.
59URL_STR = 'URL: '
60REPO_ROOT_STR = 'Repository Root: '
61
62
63def FindDepotTools():
64 """ Find depot_tools on the local machine and return its location. """
65 which_cmd = 'where' if os.name == 'nt' else 'which'
66 cmd = [which_cmd, 'gcl']
67 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
68 if proc.wait() != 0:
69 raise Exception('Couldn\'t find depot_tools in PATH!')
borenet@google.com6c55b512013-01-24 21:38:51 +000070 gcl = proc.communicate()[0].split('\n')[0].rstrip()
borenet@google.com6b5388e2013-01-23 20:54:29 +000071 depot_tools_dir = os.path.dirname(gcl)
72 return depot_tools_dir
73
74
75def GetCheckoutRoot(is_svn=True):
76 """ Determine where the local checkout is rooted.
77
78 is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in
79 a git checkout.
80 """
81 if is_svn:
borenet@google.com6c55b512013-01-24 21:38:51 +000082 svn_cmd = 'svn.bat' if os.name == 'nt' else 'svn'
83 cmd = [svn_cmd, 'info']
borenet@google.com6b5388e2013-01-23 20:54:29 +000084 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
85 stderr=subprocess.STDOUT)
86 if proc.wait() != 0:
87 raise Exception('Couldn\'t find checkout root!')
88 output = proc.communicate()[0].split('\n')
89 url = None
90 repo_root = None
91 for line in output:
92 if line.startswith(REPO_ROOT_STR):
93 repo_root = line[len(REPO_ROOT_STR):].rstrip()
94 elif line.startswith(URL_STR):
95 url = line[len(URL_STR):].rstrip()
96 if not url or not repo_root:
97 raise Exception('Couldn\'t find checkout root!')
98 if url == repo_root:
99 return 'svn'
100 return url[len(repo_root)+1:]
101 else:
102 cmd = ['git', 'rev-parse', '--show-toplevel']
103 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
104 stderr=subprocess.STDOUT)
105 if proc.wait() != 0:
106 raise Exception('Couldn\'t find checkout root!')
107 return os.path.basename(proc.communicate()[0])
108
109
110def GetTryRepo():
111 """ Determine the TRYSERVER_SVN_URL from the codereview.settings file in the
112 Skia repo. """
113 connection = httplib.HTTPConnection(SKIA_URL)
114 connection.request('GET', CODEREVIEW_SETTINGS)
115 content = connection.getresponse().read()
116 for line in content.split('\n'):
117 if line.startswith(TRYSERVER_SVN_URL):
118 return line[len(TRYSERVER_SVN_URL):].rstrip()
119 raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is '
120 'defined in the %s file.' % CODEREVIEW_SETTINGS)
121
122
123def RetrieveTrybotList():
124 """ Retrieve the list of known trybots from the build master, stripping
125 TRYBOT_SUFFIX from the name. """
126 trybots = []
127 connection = httplib.HTTPConnection(SKIA_BUILD_MASTER_HOST,
128 SKIA_BUILD_MASTER_PORT)
129 connection.request('GET', '/json/builders')
130 response = connection.getresponse()
131 builders = json.load(response)
132
133 for builder in builders:
134 if builder.endswith(TRYBOT_SUFFIX):
135 trybots.append(builder[:-len(TRYBOT_SUFFIX)])
136 return trybots
137
138
borenet@google.coma5d621f2013-01-25 20:55:35 +0000139def ValidateArgs(argv, trybots, is_svn=True):
borenet@google.com6b5388e2013-01-23 20:54:29 +0000140 """ Parse and validate command-line arguments. If the arguments are valid,
141 returns a tuple of (<changelist name>, <list of trybots>).
142
143 trybots: A list of the known try builders.
144 """
borenet@google.com6b5388e2013-01-23 20:54:29 +0000145
borenet@google.coma5d621f2013-01-25 20:55:35 +0000146 class CollectedArgs(object):
147 def __init__(self, bots, changelist, revision):
148 self._bots = bots
149 self._changelist = changelist
150 self._revision = revision
151
152 @property
153 def bots(self):
154 for bot in self._bots:
155 yield bot
156
157 @property
158 def changelist(self):
159 return self._changelist
160
161 @property
162 def revision(self):
163 return self._revision
164
165 usage = (
166"""submit_try: Submit a try request.
167submit_try %s--bot <buildername> [<buildername> ...]
168
169--bot Builder on which to run the try. Required.
170-h, --help Show this message.
171-r <revision#> Revision from which to run the try.
172-l, --list_bots List the available try builders and exit.
173""" % ('<changelist> ' if is_svn else ''))
174
175 def Error(msg=None):
176 if msg:
177 print msg
178 print usage
179 sys.exit(1)
180
181 using_bots = None
182 changelist = None
183 revision = None
184
185 while argv:
186 arg = argv.pop(0)
187 if arg == '-h' or arg == '--help':
188 Error()
189 elif arg == '-l' or arg == '--list_bots':
190 print 'submit_try: Available builders:\n %s' % '\n '.join(trybots)
191 sys.exit(0)
192 elif arg == '--bot':
193 if using_bots:
194 Error('--bot specified multiple times.')
195 if len(argv) < 1:
196 Error('You must specify a builder with "--bot".')
197 using_bots = []
198 while argv and not argv[0].startswith('-'):
199 bot = argv.pop(0)
200 if bot == ALL_BUILDERS:
201 if using_bots:
202 Error('Cannot specify "all" with additional builder names.')
203 using_bots = trybots
204 break
205 else:
206 if not bot in trybots:
207 Error('Unrecognized builder: %s' % bot)
208 using_bots.append(bot)
209 elif arg == '-r':
210 if len(argv) < 1:
211 Error('You must specify a revision with "-r".')
212 revision = argv.pop(0)
213 else:
214 if changelist or not is_svn:
215 Error('Unknown argument: %s' % arg)
216 changelist = arg
217 if is_svn and not changelist:
218 Error('You must specify a changelist name.')
219 if not using_bots:
220 Error('You must specify one or more builders using --bot.')
221 return CollectedArgs(bots=using_bots, changelist=changelist,
222 revision=revision)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000223
224
225def SubmitTryRequest(args, is_svn=True):
226 """ Submits a try request for the given changelist on the given list of
227 trybots.
228
229 args: Object whose properties are derived from command-line arguments. If
230 is_svn is True, it should contain:
231 - changelist: string; the name of the changelist to try.
232 - bot: list of strings; the names of the try builders to run.
233 - revision: optional, int; the revision number from which to run the try.
234 If is_svn is False, it should contain:
235 - bot: list of strings; the names of the try builders to run.
236 - revision: optional, int; the revision number from which to run the try.
237 is_svn: boolean; are we in an SVN repo?
238 """
borenet@google.coma5d621f2013-01-25 20:55:35 +0000239 botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots])
borenet@google.com6b5388e2013-01-23 20:54:29 +0000240 if is_svn:
borenet@google.com6c55b512013-01-24 21:38:51 +0000241 gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl'
242 try_args = [gcl_cmd, 'try', args.changelist,
243 '--root', GetCheckoutRoot(is_svn),
borenet@google.com6b5388e2013-01-23 20:54:29 +0000244 '--bot', botlist]
245 if args.revision:
246 try_args.extend(['-r', args.revision])
borenet@google.com6c55b512013-01-24 21:38:51 +0000247 print ' '.join(try_args)
248 proc = subprocess.Popen(try_args, stdout=subprocess.PIPE,
249 stderr=subprocess.STDOUT)
250 if proc.wait() != 0:
251 raise Exception('Failed to submit try request: %s' % (
252 proc.communicate()[0]))
253 print proc.communicate()[0]
borenet@google.com6b5388e2013-01-23 20:54:29 +0000254 else:
borenet@google.com6c55b512013-01-24 21:38:51 +0000255 # First, find depot_tools. This is needed to import trychange.
256 sys.path.append(FindDepotTools())
borenet@google.com6b5388e2013-01-23 20:54:29 +0000257 import trychange
258 try_args = ['--use_svn',
259 '--svn_repo', GetTryRepo(),
260 '--root', GetCheckoutRoot(is_svn),
261 '--bot', botlist]
262 if args.revision:
263 try_args.extend(['-r', args.revision])
264 trychange.TryChange(try_args, None, False)
265
266
267def main():
268 # Retrieve the list of active try builders from the build master.
269 trybots = RetrieveTrybotList()
270
271 # Determine if we're in an SVN checkout.
272 is_svn = os.path.isdir('.svn')
273
274 # Parse and validate the command-line arguments.
borenet@google.coma5d621f2013-01-25 20:55:35 +0000275 args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn)
borenet@google.com6b5388e2013-01-23 20:54:29 +0000276
277 # Submit the try request.
278 SubmitTryRequest(args, is_svn=is_svn)
279
280
281if __name__ == '__main__':
282 sys.exit(main())