blob: 289b0fe4eac275764663af747dd26bdfbc0f7bad [file] [log] [blame]
[email protected]fb2b8eb2009-04-23 21:03:421#!/usr/bin/python
2# Copyright (c) 2009 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Client-side script to send a try job to the try server. It communicates to
6the try server by either writting to a svn repository or by directly connecting
7to the server by HTTP.
8"""
9
[email protected]fb2b8eb2009-04-23 21:03:4210import datetime
[email protected]01d8c1d2010-01-07 01:56:5911import errno
[email protected]fb2b8eb2009-04-23 21:03:4212import getpass
13import logging
14import optparse
15import os
[email protected]d9141bf2009-12-23 16:13:3216import posixpath
[email protected]8ede00e2010-01-12 14:35:2817import re
[email protected]fb2b8eb2009-04-23 21:03:4218import shutil
19import sys
20import tempfile
[email protected]fb2b8eb2009-04-23 21:03:4221import urllib
22
[email protected]ea8c1a92009-12-20 17:21:5923try:
[email protected]57af1712010-03-18 00:31:5124 import simplejson as json
25except ImportError:
26 try:
27 import json
28 except ImportError:
29 json = None
30
31try:
[email protected]ea8c1a92009-12-20 17:21:5932 import breakpad
33except ImportError:
34 pass
[email protected]82f2c082009-12-08 21:25:3735
[email protected]9a2f37e2009-12-19 16:03:2836import gclient_utils
[email protected]5aeb7dd2009-11-17 18:09:0137import scm
[email protected]fb2b8eb2009-04-23 21:03:4238
[email protected]18111352009-12-20 17:21:2839__version__ = '1.2'
[email protected]fb2b8eb2009-04-23 21:03:4240
41
42# Constants
43HELP_STRING = "Sorry, Tryserver is not available."
[email protected]d9141bf2009-12-23 16:13:3244USAGE = r"""%prog [options]
[email protected]fb2b8eb2009-04-23 21:03:4245
46Client-side script to send a try job to the try server. It communicates to
47the try server by either writting to a svn repository or by directly connecting
[email protected]d0891922010-05-31 18:33:1648to the server by HTTP."""
[email protected]fb2b8eb2009-04-23 21:03:4249
[email protected]d0891922010-05-31 18:33:1650EPILOG = """
[email protected]fb2b8eb2009-04-23 21:03:4251Examples:
[email protected]d0891922010-05-31 18:33:1652 Send a patch directly from rietveld:
53 %(prog)s -R codereview.chromium.org/1337
54 --email [email protected] --root src
55
[email protected]4e9f4912009-10-23 19:37:3356 Try a change against a particular revision:
[email protected]d0891922010-05-31 18:33:1657 %(prog)s -r 123
[email protected]4e9f4912009-10-23 19:37:3358
[email protected]fb2b8eb2009-04-23 21:03:4259 A git patch off a web site (git inserts a/ and b/) and fix the base dir:
[email protected]d0891922010-05-31 18:33:1660 %(prog)s --url http://url/to/patch.diff --patchlevel 1 --root src
[email protected]5f423142010-01-30 05:46:3661
[email protected]fb2b8eb2009-04-23 21:03:4262 Use svn to store the try job, specify an alternate email address and use a
63 premade diff file on the local drive:
[email protected]d0891922010-05-31 18:33:1664 %(prog)s --email [email protected]
[email protected]fb2b8eb2009-04-23 21:03:4265 --svn_repo svn://svn.chromium.org/chrome-try/try --diff foo.diff
66
[email protected]4e9f4912009-10-23 19:37:3367 Running only on a 'mac' slave with revision 123 and clobber first; specify
[email protected]fb2b8eb2009-04-23 21:03:4268 manually the 3 source files to use for the try job:
[email protected]d0891922010-05-31 18:33:1669 %(prog)s --bot mac --revision 123 --clobber -f src/a.cc -f src/a.h
[email protected]d9141bf2009-12-23 16:13:3270 -f include/b.h
[email protected]d9141bf2009-12-23 16:13:3271"""
[email protected]fb2b8eb2009-04-23 21:03:4272
73class InvalidScript(Exception):
74 def __str__(self):
75 return self.args[0] + '\n' + HELP_STRING
76
77
78class NoTryServerAccess(Exception):
79 def __str__(self):
80 return self.args[0] + '\n' + HELP_STRING
81
82
[email protected]fb2b8eb2009-04-23 21:03:4283def EscapeDot(name):
84 return name.replace('.', '-')
85
86
[email protected]fb2b8eb2009-04-23 21:03:4287class SCM(object):
88 """Simplistic base class to implement one function: ProcessOptions."""
[email protected]1c7db8e2010-01-07 02:00:1989 def __init__(self, options, path):
90 items = path.split('@')
91 assert len(items) <= 2
92 self.checkout_root = items[0]
93 items.append(None)
94 self.diff_against = items[1]
[email protected]fb2b8eb2009-04-23 21:03:4295 self.options = options
[email protected]f7ae6d52009-12-22 20:49:0496 self.files = self.options.files
97 self.options.files = None
[email protected]d9141bf2009-12-23 16:13:3298 self.codereview_settings = None
99 self.codereview_settings_file = 'codereview.settings'
[email protected]2b9aa8e2010-08-25 20:01:42100 self.gclient_root = None
[email protected]fb2b8eb2009-04-23 21:03:42101
[email protected]18111352009-12-20 17:21:28102 def GetFileNames(self):
103 """Return the list of files in the diff."""
[email protected]f7ae6d52009-12-22 20:49:04104 return self.files
[email protected]fb2b8eb2009-04-23 21:03:42105
[email protected]d9141bf2009-12-23 16:13:32106 def GetCodeReviewSetting(self, key):
107 """Returns a value for the given key for this repository.
108
109 Uses gcl-style settings from the repository."""
110 if self.codereview_settings is None:
111 self.codereview_settings = {}
112 settings_file = self.ReadRootFile(self.codereview_settings_file)
113 if settings_file:
114 for line in settings_file.splitlines():
115 if not line or line.lstrip().startswith('#'):
116 continue
117 k, v = line.split(":", 1)
118 self.codereview_settings[k.strip()] = v.strip()
119 return self.codereview_settings.get(key, '')
120
[email protected]28a5a522010-05-08 00:14:05121 def _GclStyleSettings(self):
[email protected]d9141bf2009-12-23 16:13:32122 """Set default settings based on the gcl-style settings from the
123 repository."""
124 settings = {
125 'port': self.GetCodeReviewSetting('TRYSERVER_HTTP_PORT'),
126 'host': self.GetCodeReviewSetting('TRYSERVER_HTTP_HOST'),
127 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'),
128 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'),
129 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'),
130 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'),
131 }
[email protected]28a5a522010-05-08 00:14:05132 logging.info('\n'.join(['%s: %s' % (k, v)
133 for (k, v) in settings.iteritems() if v]))
[email protected]d9141bf2009-12-23 16:13:32134 for (k, v) in settings.iteritems():
135 if v and getattr(self.options, k) is None:
136 setattr(self.options, k, v)
137
[email protected]28a5a522010-05-08 00:14:05138 def _GclientStyleSettings(self):
[email protected]d9141bf2009-12-23 16:13:32139 """Find the root, assuming a gclient-style checkout."""
140 if not self.options.no_gclient and not self.options.root:
[email protected]01d8c1d2010-01-07 01:56:59141 root = self.checkout_root
[email protected]28a5a522010-05-08 00:14:05142 self.gclient_root = gclient_utils.FindGclientRoot(root)
143 if self.gclient_root:
144 logging.info('Found .gclient at %s' % self.gclient_root)
145 self.options.root = gclient_utils.PathDifference(self.gclient_root,
146 root)
[email protected]d9141bf2009-12-23 16:13:32147
148 def AutomagicalSettings(self):
149 """Determines settings based on supported code review and checkout tools.
150 """
[email protected]28a5a522010-05-08 00:14:05151 self._GclientStyleSettings()
152 self._GclStyleSettings()
[email protected]d9141bf2009-12-23 16:13:32153
[email protected]01d8c1d2010-01-07 01:56:59154 def ReadRootFile(self, filename):
[email protected]28a5a522010-05-08 00:14:05155 if not self.options.root:
156 filepath = os.path.join(self.checkout_root, filename)
157 if os.path.isfile(filepath):
158 logging.info('Found %s at %s' % (filename, self.checkout_root))
[email protected]a8e36cb2010-05-27 23:47:18159 return gclient_utils.FileRead(filepath)
[email protected]28a5a522010-05-08 00:14:05160 return None
[email protected]28a5a522010-05-08 00:14:05161 cur = os.path.abspath(self.checkout_root)
[email protected]258d17f2010-05-17 17:30:33162 if self.gclient_root:
163 root = os.path.abspath(self.gclient_root)
164 else:
165 root = gclient_utils.FindGclientRoot(cur)
[email protected]21779a52010-09-02 00:40:53166 if not root:
167 root = cur
[email protected]28a5a522010-05-08 00:14:05168 assert cur.startswith(root), (root, cur)
169 while cur.startswith(root):
170 filepath = os.path.join(cur, filename)
171 if os.path.isfile(filepath):
172 logging.info('Found %s at %s' % (filename, cur))
173 return gclient_utils.FileRead(filepath)
174 cur = os.path.dirname(cur)
175 logging.warning('Didn\'t find %s' % filename)
176 return None
[email protected]01d8c1d2010-01-07 01:56:59177
[email protected]fb2b8eb2009-04-23 21:03:42178
179class SVN(SCM):
180 """Gathers the options and diff for a subversion checkout."""
[email protected]18111352009-12-20 17:21:28181 def __init__(self, *args, **kwargs):
182 SCM.__init__(self, *args, **kwargs)
[email protected]f7ae6d52009-12-22 20:49:04183 self.checkout_root = scm.SVN.GetCheckoutRoot(self.checkout_root)
[email protected]18111352009-12-20 17:21:28184 if not self.options.email:
185 # Assumes the svn credential is an email address.
186 self.options.email = scm.SVN.GetEmail(self.checkout_root)
[email protected]01d8c1d2010-01-07 01:56:59187 logging.info("SVN(%s)" % self.checkout_root)
[email protected]18111352009-12-20 17:21:28188
[email protected]d9141bf2009-12-23 16:13:32189 def ReadRootFile(self, filename):
[email protected]28a5a522010-05-08 00:14:05190 data = SCM.ReadRootFile(self, filename)
191 if data:
[email protected]d9141bf2009-12-23 16:13:32192 return data
[email protected]28a5a522010-05-08 00:14:05193
194 # Try to search on the subversion repository for the file.
195 try:
196 from gcl import GetCachedFile
[email protected]d9141bf2009-12-23 16:13:32197 except ImportError:
[email protected]28a5a522010-05-08 00:14:05198 return None
199 data = GetCachedFile(filename)
200 logging.debug('%s:\n%s' % (filename, data))
201 return data
[email protected]d9141bf2009-12-23 16:13:32202
[email protected]f7ae6d52009-12-22 20:49:04203 def GenerateDiff(self):
[email protected]fb2b8eb2009-04-23 21:03:42204 """Returns a string containing the diff for the given file list.
205
206 The files in the list should either be absolute paths or relative to the
[email protected]18111352009-12-20 17:21:28207 given root.
[email protected]fb2b8eb2009-04-23 21:03:42208 """
[email protected]f7ae6d52009-12-22 20:49:04209 if not self.files:
[email protected]f2f9d552009-12-22 00:12:57210 previous_cwd = os.getcwd()
211 os.chdir(self.checkout_root)
[email protected]8ede00e2010-01-12 14:35:28212
[email protected]f2f9d552009-12-22 00:12:57213 excluded = ['!', '?', 'X', ' ', '~']
[email protected]8ede00e2010-01-12 14:35:28214 def Excluded(f):
215 if f[0][0] in excluded:
216 return True
217 for r in self.options.exclude:
218 if re.search(r, f[1]):
219 logging.info('Ignoring "%s"' % f[1])
220 return True
221 return False
222
223 self.files = [f[1] for f in scm.SVN.CaptureStatus(self.checkout_root)
224 if not Excluded(f)]
[email protected]f2f9d552009-12-22 00:12:57225 os.chdir(previous_cwd)
[email protected]1c7db8e2010-01-07 02:00:19226 return scm.SVN.GenerateDiff(self.files, self.checkout_root, full_move=True,
227 revision=self.diff_against)
[email protected]fb2b8eb2009-04-23 21:03:42228
[email protected]fb2b8eb2009-04-23 21:03:42229
230class GIT(SCM):
231 """Gathers the options and diff for a git checkout."""
[email protected]18111352009-12-20 17:21:28232 def __init__(self, *args, **kwargs):
233 SCM.__init__(self, *args, **kwargs)
[email protected]f7ae6d52009-12-22 20:49:04234 self.checkout_root = scm.GIT.GetCheckoutRoot(self.checkout_root)
[email protected]18111352009-12-20 17:21:28235 if not self.options.name:
[email protected]b24a8e12009-12-22 13:45:48236 self.options.name = scm.GIT.GetPatchName(self.checkout_root)
[email protected]18111352009-12-20 17:21:28237 if not self.options.email:
[email protected]b24a8e12009-12-22 13:45:48238 self.options.email = scm.GIT.GetEmail(self.checkout_root)
[email protected]81e012c2010-04-29 16:07:24239 if not self.diff_against:
240 self.diff_against = scm.GIT.GetUpstreamBranch(self.checkout_root)
241 if not self.diff_against:
[email protected]a630bd72010-04-29 23:32:34242 raise NoTryServerAccess(
243 "Unable to determine default branch to diff against. "
244 "Verify this branch is set up to track another"
245 "(via the --track argument to \"git checkout -b ...\"")
[email protected]01d8c1d2010-01-07 01:56:59246 logging.info("GIT(%s)" % self.checkout_root)
[email protected]fb2b8eb2009-04-23 21:03:42247
[email protected]f7ae6d52009-12-22 20:49:04248 def GenerateDiff(self):
[email protected]8ede00e2010-01-12 14:35:28249 if not self.files:
250 self.files = scm.GIT.GetDifferentFiles(self.checkout_root,
251 branch=self.diff_against)
252
253 def NotExcluded(f):
254 for r in self.options.exclude:
255 if re.search(r, f):
256 logging.info('Ignoring "%s"' % f)
257 return False
258 return True
259
260 self.files = filter(NotExcluded, self.files)
261 return scm.GIT.GenerateDiff(self.checkout_root, files=self.files,
262 full_move=True,
[email protected]1c7db8e2010-01-07 02:00:19263 branch=self.diff_against)
[email protected]f7ae6d52009-12-22 20:49:04264
[email protected]fb2b8eb2009-04-23 21:03:42265
266def _ParseSendChangeOptions(options):
267 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN."""
268 values = {}
269 if options.email:
270 values['email'] = options.email
271 values['user'] = options.user
272 values['name'] = options.name
273 if options.bot:
274 values['bot'] = ','.join(options.bot)
275 if options.revision:
276 values['revision'] = options.revision
277 if options.clobber:
278 values['clobber'] = 'true'
[email protected]c3057682010-05-01 01:47:02279 if options.testfilter:
280 values['testfilter'] = ','.join(options.testfilter)
[email protected]fb2b8eb2009-04-23 21:03:42281 if options.root:
282 values['root'] = options.root
283 if options.patchlevel:
284 values['patchlevel'] = options.patchlevel
285 if options.issue:
286 values['issue'] = options.issue
287 if options.patchset:
288 values['patchset'] = options.patchset
[email protected]b3b00a42009-07-03 00:47:34289 if options.target:
290 values['target'] = options.target
[email protected]833b2272009-09-10 18:30:23291 if options.project:
292 values['project'] = options.project
[email protected]fb2b8eb2009-04-23 21:03:42293 return values
294
295
296def _SendChangeHTTP(options):
297 """Send a change to the try server using the HTTP protocol."""
[email protected]fb2b8eb2009-04-23 21:03:42298 if not options.host:
[email protected]f7fb33e2009-06-17 12:59:15299 raise NoTryServerAccess('Please use the --host option to specify the try '
300 'server host to connect to.')
[email protected]fb2b8eb2009-04-23 21:03:42301 if not options.port:
[email protected]f7fb33e2009-06-17 12:59:15302 raise NoTryServerAccess('Please use the --port option to specify the try '
303 'server port to connect to.')
[email protected]fb2b8eb2009-04-23 21:03:42304
305 values = _ParseSendChangeOptions(options)
[email protected]2b9aa8e2010-08-25 20:01:42306 description = ''.join("%s=%s\n" % (k, v) for (k, v) in values.iteritems())
[email protected]fb2b8eb2009-04-23 21:03:42307 values['patch'] = options.diff
308
309 url = 'http://%s:%s/send_try_patch' % (options.host, options.port)
310 proxies = None
311 if options.proxy:
312 if options.proxy.lower() == 'none':
313 # Effectively disable HTTP_PROXY or Internet settings proxy setup.
314 proxies = {}
315 else:
316 proxies = {'http': options.proxy, 'https': options.proxy}
[email protected]c308a742009-12-22 18:29:33317
[email protected]7538b862010-01-08 21:41:33318 logging.info('Sending by HTTP')
[email protected]d9141bf2009-12-23 16:13:32319 logging.info(description)
320 logging.info(url)
321 logging.info(options.diff)
[email protected]c308a742009-12-22 18:29:33322 if options.dry_run:
[email protected]c308a742009-12-22 18:29:33323 return
324
[email protected]fb2b8eb2009-04-23 21:03:42325 try:
326 connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies)
327 except IOError, e:
[email protected]d9141bf2009-12-23 16:13:32328 logging.warning(str(e))
[email protected]cce9e1c2009-10-14 00:50:22329 if (values.get('bot') and len(e.args) > 2 and
330 e.args[2] == 'got a bad status line'):
[email protected]fb2b8eb2009-04-23 21:03:42331 raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url)
332 else:
[email protected]cce9e1c2009-10-14 00:50:22333 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url,
334 str(e.args)))
[email protected]fb2b8eb2009-04-23 21:03:42335 if not connection:
336 raise NoTryServerAccess('%s is unaccessible.' % url)
[email protected]6f6a8012009-12-22 18:48:39337 response = connection.read()
338 if response != 'OK':
339 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response))
[email protected]fb2b8eb2009-04-23 21:03:42340
341
342def _SendChangeSVN(options):
343 """Send a change to the try server by committing a diff file on a subversion
344 server."""
[email protected]fb2b8eb2009-04-23 21:03:42345 if not options.svn_repo:
[email protected]f7fb33e2009-06-17 12:59:15346 raise NoTryServerAccess('Please use the --svn_repo option to specify the'
347 ' try server svn repository to connect to.')
[email protected]fb2b8eb2009-04-23 21:03:42348
349 values = _ParseSendChangeOptions(options)
[email protected]2b9aa8e2010-08-25 20:01:42350 description = ''.join("%s=%s\n" % (k, v) for (k, v) in values.iteritems())
[email protected]7538b862010-01-08 21:41:33351 logging.info('Sending by SVN')
[email protected]d9141bf2009-12-23 16:13:32352 logging.info(description)
353 logging.info(options.svn_repo)
354 logging.info(options.diff)
[email protected]c308a742009-12-22 18:29:33355 if options.dry_run:
[email protected]c308a742009-12-22 18:29:33356 return
357
[email protected]34d73cb2010-04-28 16:39:56358 # Create a temporary directory, put a uniquely named file in it with the diff
359 # content and svn import that.
[email protected]fb2b8eb2009-04-23 21:03:42360 temp_dir = tempfile.mkdtemp()
361 temp_file = tempfile.NamedTemporaryFile()
[email protected]fb2b8eb2009-04-23 21:03:42362 try:
[email protected]fb2b8eb2009-04-23 21:03:42363 try:
[email protected]34d73cb2010-04-28 16:39:56364 # Description
365 temp_file.write(description)
366 temp_file.flush()
[email protected]9a2f37e2009-12-19 16:03:28367
[email protected]34d73cb2010-04-28 16:39:56368 # Diff file
[email protected]9a2f37e2009-12-19 16:03:28369 current_time = str(datetime.datetime.now()).replace(':', '.')
370 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) +
371 '.%s.diff' % current_time)
372 full_path = os.path.join(temp_dir, file_name)
[email protected]34d73cb2010-04-28 16:39:56373 gclient_utils.FileWrite(full_path, options.diff, 'wb')
374
375 # Committing it will trigger a try job.
[email protected]1dd0eea2010-04-28 20:13:42376 if sys.platform == "cygwin":
377 # Small chromium-specific issue here:
378 # git-try uses /usr/bin/python on cygwin but svn.bat will be used
379 # instead of /usr/bin/svn by default. That causes bad things(tm) since
380 # Windows' svn.exe has no clue about cygwin paths. Hence force to use
381 # the cygwin version in this particular context.
382 exe = "/usr/bin/svn"
383 else:
384 exe = "svn"
385 command = [exe, 'import', '-q', temp_dir, options.svn_repo, '--file',
[email protected]34d73cb2010-04-28 16:39:56386 temp_file.name]
387 gclient_utils.CheckCall(command)
[email protected]9a2f37e2009-12-19 16:03:28388 except gclient_utils.CheckCallError, e:
[email protected]6fb10902010-03-24 19:16:28389 out = e.stdout
390 if e.stderr:
391 out += e.stderr
392 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' + out)
[email protected]fb2b8eb2009-04-23 21:03:42393 finally:
394 temp_file.close()
395 shutil.rmtree(temp_dir, True)
[email protected]fb2b8eb2009-04-23 21:03:42396
397
[email protected]b6d36492009-12-26 14:22:41398def PrintSuccess(options):
399 if not options.dry_run:
400 text = 'Patch \'%s\' sent to try server' % options.name
401 if options.bot:
402 text += ': %s' % ', '.join(options.bot)
403 print(text)
404
405
[email protected]01d8c1d2010-01-07 01:56:59406def GuessVCS(options, path):
[email protected]fb2b8eb2009-04-23 21:03:42407 """Helper to guess the version control system.
408
409 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't
410 support it yet.
411
[email protected]01d8c1d2010-01-07 01:56:59412 This examines the path directory, guesses which SCM we're using, and
[email protected]fb2b8eb2009-04-23 21:03:42413 returns an instance of the appropriate class. Exit with an error if we can't
414 figure it out.
415
416 Returns:
417 A SCM instance. Exits if the SCM can't be guessed.
418 """
[email protected]5aeb7dd2009-11-17 18:09:01419 __pychecker__ = 'no-returnvalues'
[email protected]1c7db8e2010-01-07 02:00:19420 real_path = path.split('@')[0]
[email protected]01d8c1d2010-01-07 01:56:59421 logging.info("GuessVCS(%s)" % path)
[email protected]fb2b8eb2009-04-23 21:03:42422 # Subversion has a .svn in all working directories.
[email protected]1c7db8e2010-01-07 02:00:19423 if os.path.isdir(os.path.join(real_path, '.svn')):
[email protected]01d8c1d2010-01-07 01:56:59424 return SVN(options, path)
[email protected]fb2b8eb2009-04-23 21:03:42425
426 # Git has a command to test if you're in a git tree.
427 # Try running it, but don't die if we don't have git installed.
428 try:
[email protected]01d8c1d2010-01-07 01:56:59429 gclient_utils.CheckCall(["git", "rev-parse", "--is-inside-work-tree"],
[email protected]1c7db8e2010-01-07 02:00:19430 real_path)
[email protected]01d8c1d2010-01-07 01:56:59431 return GIT(options, path)
[email protected]18111352009-12-20 17:21:28432 except gclient_utils.CheckCallError, e:
[email protected]66c83e62010-09-07 14:18:45433 if e.returncode != errno.ENOENT and e.returncode != 128:
[email protected]01d8c1d2010-01-07 01:56:59434 # ENOENT == 2 = they don't have git installed.
435 # 128 = git error code when not in a repo.
[email protected]37e89872010-09-07 16:11:33436 logging.warn('Unexpected error code: %s' % e.returncode)
[email protected]fb2b8eb2009-04-23 21:03:42437 raise
[email protected]fb2b8eb2009-04-23 21:03:42438 raise NoTryServerAccess("Could not guess version control system. "
439 "Are you in a working copy directory?")
440
441
[email protected]c2190cb2010-01-10 01:57:06442def GetMungedDiff(path_diff, diff):
443 # Munge paths to match svn.
444 for i in range(len(diff)):
445 if diff[i].startswith('--- ') or diff[i].startswith('+++ '):
446 new_file = posixpath.join(path_diff, diff[i][4:]).replace('\\', '/')
447 diff[i] = diff[i][0:4] + new_file
448 return diff
449
450
[email protected]fb2b8eb2009-04-23 21:03:42451def TryChange(argv,
452 file_list,
453 swallow_exception,
[email protected]d0891922010-05-31 18:33:16454 prog=None,
455 extra_epilog=None):
[email protected]de243452009-10-06 21:02:56456 """
457 Args:
458 argv: Arguments and options.
459 file_list: Default value to pass to --file.
460 swallow_exception: Whether we raise or swallow exceptions.
461 """
[email protected]fb2b8eb2009-04-23 21:03:42462 # Parse argv
[email protected]1e3b8f02010-05-31 23:47:51463 parser = optparse.OptionParser(usage=USAGE,
464 version=__version__,
465 prog=prog)
[email protected]d0891922010-05-31 18:33:16466 epilog = EPILOG % { 'prog': prog }
467 if extra_epilog:
468 epilog += extra_epilog
[email protected]1e3b8f02010-05-31 23:47:51469 parser.epilog = epilog
[email protected]d0891922010-05-31 18:33:16470 # Remove epilog formatting
471 parser.format_epilog = lambda x: parser.epilog
[email protected]d9141bf2009-12-23 16:13:32472 parser.add_option("-v", "--verbose", action="count", default=0,
473 help="Prints debugging infos")
[email protected]fb2b8eb2009-04-23 21:03:42474 group = optparse.OptionGroup(parser, "Result and status")
475 group.add_option("-u", "--user", default=getpass.getuser(),
476 help="Owner user name [default: %default]")
[email protected]e1555a62009-11-09 17:05:54477 group.add_option("-e", "--email",
478 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
479 os.environ.get('EMAIL_ADDRESS')),
480 help="Email address where to send the results. Use either "
481 "the TRYBOT_RESULTS_EMAIL_ADDRESS environment "
482 "variable or EMAIL_ADDRESS to set the email address "
483 "the try bots report results to [default: %default]")
[email protected]c9f06b02009-11-03 19:32:08484 group.add_option("-n", "--name",
[email protected]fb2b8eb2009-04-23 21:03:42485 help="Descriptive name of the try job")
486 group.add_option("--issue", type='int',
487 help="Update rietveld issue try job status")
488 group.add_option("--patchset", type='int',
[email protected]5f423142010-01-30 05:46:36489 help="Update rietveld issue try job status. This is "
490 "optional if --issue is used, In that case, the "
491 "latest patchset will be used.")
[email protected]c308a742009-12-22 18:29:33492 group.add_option("--dry_run", action='store_true',
493 help="Just prints the diff and quits")
[email protected]fb2b8eb2009-04-23 21:03:42494 parser.add_option_group(group)
495
496 group = optparse.OptionGroup(parser, "Try job options")
497 group.add_option("-b", "--bot", action="append",
498 help="Only use specifics build slaves, ex: '--bot win' to "
499 "run the try job only on the 'win' slave; see the try "
[email protected]833b2272009-09-10 18:30:23500 "server waterfall for the slave's name")
[email protected]fb2b8eb2009-04-23 21:03:42501 group.add_option("-r", "--revision",
502 help="Revision to use for the try job; default: the "
503 "revision will be determined by the try server; see "
504 "its waterfall for more info")
505 group.add_option("-c", "--clobber", action="store_true",
506 help="Force a clobber before building; e.g. don't do an "
507 "incremental build")
[email protected]b3b00a42009-07-03 00:47:34508 # TODO(maruel): help="Select a specific configuration, usually 'debug' or "
509 # "'release'"
510 group.add_option("--target", help=optparse.SUPPRESS_HELP)
511
[email protected]ea8c1a92009-12-20 17:21:59512 group.add_option("--project",
[email protected]c308a742009-12-22 18:29:33513 help="Override which project to use. Projects are defined "
514 "server-side to define what default bot set to use")
[email protected]833b2272009-09-10 18:30:23515
[email protected]c3057682010-05-01 01:47:02516 group.add_option("-t", "--testfilter", action="append",
517 help="Add a gtest_filter to a test. Use multiple times to "
518 "specify filters for different tests. (i.e. "
519 "--testfilter base_unittests:ThreadTest.* "
520 "--testfilter ui_tests) If you specify any testfilters "
521 "the test results will not be reported in rietveld and "
522 "only tests with filters will run.")
523
[email protected]fb2b8eb2009-04-23 21:03:42524 parser.add_option_group(group)
525
526 group = optparse.OptionGroup(parser, "Patch to run")
527 group.add_option("-f", "--file", default=file_list, dest="files",
528 metavar="FILE", action="append",
529 help="Use many times to list the files to include in the "
530 "try, relative to the repository root")
531 group.add_option("--diff",
532 help="File containing the diff to try")
533 group.add_option("--url",
[email protected]d0891922010-05-31 18:33:16534 help="Url where to grab a patch, e.g. "
535 "http://example.com/x.diff")
536 group.add_option("-R", "--rietveld_url", default="codereview.appspot.com",
537 metavar="URL",
538 help="Has 2 usages, both refer to the rietveld instance: "
539 "Specify which code review patch to use as the try job "
540 "or rietveld instance to update the try job results "
541 "Default:%default")
[email protected]fb2b8eb2009-04-23 21:03:42542 group.add_option("--root",
543 help="Root to use for the patch; base subdirectory for "
[email protected]ea8c1a92009-12-20 17:21:59544 "patch created in a subdirectory")
[email protected]01d8c1d2010-01-07 01:56:59545 group.add_option("-p", "--patchlevel", type='int', metavar="LEVEL",
[email protected]ea8c1a92009-12-20 17:21:59546 help="Used as -pN parameter to patch")
[email protected]01d8c1d2010-01-07 01:56:59547 group.add_option("-s", "--sub_rep", action="append", default=[],
[email protected]f7ae6d52009-12-22 20:49:04548 help="Subcheckout to use in addition. This is mainly "
[email protected]1c7db8e2010-01-07 02:00:19549 "useful for gclient-style checkouts. Use @rev or "
550 "@branch or @branch1..branch2 to specify the "
551 "revision/branch to diff against.")
[email protected]d9141bf2009-12-23 16:13:32552 group.add_option("--no_gclient", action="store_true",
553 help="Disable automatic search for gclient checkout.")
[email protected]8ede00e2010-01-12 14:35:28554 group.add_option("-E", "--exclude", action="append",
555 default=['ChangeLog'], metavar='REGEXP',
556 help="Regexp patterns to exclude files. Default: %default")
[email protected]fb2b8eb2009-04-23 21:03:42557 parser.add_option_group(group)
558
559 group = optparse.OptionGroup(parser, "Access the try server by HTTP")
[email protected]f7fb33e2009-06-17 12:59:15560 group.add_option("--use_http",
561 action="store_const",
562 const=_SendChangeHTTP,
563 dest="send_patch",
[email protected]fb2b8eb2009-04-23 21:03:42564 help="Use HTTP to talk to the try server [default]")
[email protected]01d8c1d2010-01-07 01:56:59565 group.add_option("-H", "--host",
[email protected]fb2b8eb2009-04-23 21:03:42566 help="Host address")
[email protected]01d8c1d2010-01-07 01:56:59567 group.add_option("-P", "--port",
[email protected]fb2b8eb2009-04-23 21:03:42568 help="HTTP port")
569 group.add_option("--proxy",
570 help="HTTP proxy")
571 parser.add_option_group(group)
572
573 group = optparse.OptionGroup(parser, "Access the try server with SVN")
[email protected]f7fb33e2009-06-17 12:59:15574 group.add_option("--use_svn",
575 action="store_const",
576 const=_SendChangeSVN,
[email protected]fb2b8eb2009-04-23 21:03:42577 dest="send_patch",
578 help="Use SVN to talk to the try server")
[email protected]01d8c1d2010-01-07 01:56:59579 group.add_option("-S", "--svn_repo",
[email protected]f7fb33e2009-06-17 12:59:15580 metavar="SVN_URL",
[email protected]fb2b8eb2009-04-23 21:03:42581 help="SVN url to use to write the changes in; --use_svn is "
582 "implied when using --svn_repo")
583 parser.add_option_group(group)
584
585 options, args = parser.parse_args(argv)
[email protected]d05e5fb2010-07-30 20:44:14586
587 # Note that the args array includes the script name, so
588 # a single argument results in len(args) == 2.
589
590 # If they've asked for help, give it to them
591 if len(args) == 2 and args[1] == 'help':
[email protected]3ccbf7e2009-12-22 20:46:42592 parser.print_help()
[email protected]d05e5fb2010-07-30 20:44:14593 return 0
594
595 # If they've said something confusing, don't spawn a try job until you
596 # understand what they want.
597 if len(args) > 1:
598 plural = ""
599 if len(args) > 2:
600 plural = "s"
601 print "Argument%s \"%s\" not understood" % (plural, " ".join(args[1:]))
602 parser.print_help()
603 return 1
[email protected]f7fb33e2009-06-17 12:59:15604
[email protected]2c0b77d2010-06-07 13:35:41605 LOG_FORMAT = '%(levelname)s %(filename)s(%(lineno)d): %(message)s'
[email protected]01d8c1d2010-01-07 01:56:59606 if not swallow_exception:
607 if options.verbose == 0:
[email protected]2c0b77d2010-06-07 13:35:41608 logging.basicConfig(level=logging.WARNING, format=LOG_FORMAT)
[email protected]28a5a522010-05-08 00:14:05609 elif options.verbose == 1:
[email protected]2c0b77d2010-06-07 13:35:41610 logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
[email protected]28a5a522010-05-08 00:14:05611 elif options.verbose > 1:
[email protected]2c0b77d2010-06-07 13:35:41612 logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
[email protected]fb2b8eb2009-04-23 21:03:42613
[email protected]7538b862010-01-08 21:41:33614 logging.debug(argv)
615
[email protected]58a7c7d2010-05-31 01:15:18616 # Strip off any @ in the user, otherwise svn gets confused.
617 options.user = options.user.split('@', 1)[0]
618
[email protected]5f423142010-01-30 05:46:36619 if options.rietveld_url:
620 # Try to extract the review number if possible and fix the protocol.
621 if not '://' in options.rietveld_url:
622 options.rietveld_url = 'http://' + options.rietveld_url
623 match = re.match(r'^(.*)/(\d+)$', options.rietveld_url)
624 if match:
625 if options.issue or options.patchset:
626 parser.error('Cannot use both --issue and use a review number url')
627 options.issue = int(match.group(2))
628 options.rietveld_url = match.group(1)
629
[email protected]fb2b8eb2009-04-23 21:03:42630 try:
[email protected]d9141bf2009-12-23 16:13:32631 # Always include os.getcwd() in the checkout settings.
[email protected]f7ae6d52009-12-22 20:49:04632 checkouts = []
[email protected]d9141bf2009-12-23 16:13:32633 checkouts.append(GuessVCS(options, os.getcwd()))
634 checkouts[0].AutomagicalSettings()
[email protected]f7ae6d52009-12-22 20:49:04635 for item in options.sub_rep:
[email protected]01d8c1d2010-01-07 01:56:59636 checkout = GuessVCS(options, os.path.join(checkouts[0].checkout_root,
637 item))
638 if checkout.checkout_root in [c.checkout_root for c in checkouts]:
[email protected]f7ae6d52009-12-22 20:49:04639 parser.error('Specified the root %s two times.' %
[email protected]01d8c1d2010-01-07 01:56:59640 checkout.checkout_root)
[email protected]f7ae6d52009-12-22 20:49:04641 checkouts.append(checkout)
642
[email protected]b6d36492009-12-26 14:22:41643 can_http = options.port and options.host
644 can_svn = options.svn_repo
[email protected]d9141bf2009-12-23 16:13:32645 # If there was no transport selected yet, now we must have enough data to
646 # select one.
[email protected]b6d36492009-12-26 14:22:41647 if not options.send_patch and not (can_http or can_svn):
648 parser.error('Please specify an access method.')
[email protected]d9141bf2009-12-23 16:13:32649
[email protected]fb2b8eb2009-04-23 21:03:42650 # Convert options.diff into the content of the diff.
651 if options.url:
[email protected]3ccbf7e2009-12-22 20:46:42652 if options.files:
653 parser.error('You cannot specify files and --url at the same time.')
[email protected]fb2b8eb2009-04-23 21:03:42654 options.diff = urllib.urlopen(options.url).read()
655 elif options.diff:
[email protected]3ccbf7e2009-12-22 20:46:42656 if options.files:
657 parser.error('You cannot specify files and --diff at the same time.')
[email protected]9a2f37e2009-12-19 16:03:28658 options.diff = gclient_utils.FileRead(options.diff, 'rb')
[email protected]346737c2010-01-10 05:41:24659 elif options.issue and options.patchset is None:
[email protected]c2190cb2010-01-10 01:57:06660 # Retrieve the patch from rietveld when the diff is not specified.
[email protected]5f423142010-01-30 05:46:36661 # When patchset is specified, it's because it's done by gcl/git-try.
[email protected]57af1712010-03-18 00:31:51662 if json is None:
663 parser.error('json or simplejson library is missing, please install.')
[email protected]5f423142010-01-30 05:46:36664 api_url = '%s/api/%d' % (options.rietveld_url, options.issue)
665 logging.debug(api_url)
[email protected]57af1712010-03-18 00:31:51666 contents = json.loads(urllib.urlopen(api_url).read())
[email protected]5f423142010-01-30 05:46:36667 options.patchset = contents['patchsets'][-1]
668 diff_url = ('%s/download/issue%d_%d.diff' %
669 (options.rietveld_url, options.issue, options.patchset))
[email protected]c2190cb2010-01-10 01:57:06670 diff = GetMungedDiff('', urllib.urlopen(diff_url).readlines())
671 options.diff = ''.join(diff)
[email protected]f7ae6d52009-12-22 20:49:04672 else:
673 # Use this as the base.
[email protected]01d8c1d2010-01-07 01:56:59674 root = checkouts[0].checkout_root
[email protected]f7ae6d52009-12-22 20:49:04675 diffs = []
676 for checkout in checkouts:
677 diff = checkout.GenerateDiff().splitlines(True)
[email protected]01d8c1d2010-01-07 01:56:59678 path_diff = gclient_utils.PathDifference(root, checkout.checkout_root)
[email protected]c2190cb2010-01-10 01:57:06679 # Munge it.
680 diffs.extend(GetMungedDiff(path_diff, diff))
[email protected]f7ae6d52009-12-22 20:49:04681 options.diff = ''.join(diffs)
[email protected]fb2b8eb2009-04-23 21:03:42682
[email protected]de243452009-10-06 21:02:56683 if not options.bot:
[email protected]f7ae6d52009-12-22 20:49:04684 # Get try slaves from PRESUBMIT.py files if not specified.
[email protected]ea8c1a92009-12-20 17:21:59685 # Even if the diff comes from options.url, use the local checkout for bot
686 # selection.
687 try:
[email protected]ea8c1a92009-12-20 17:21:59688 import presubmit_support
[email protected]d9141bf2009-12-23 16:13:32689 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py')
[email protected]ea8c1a92009-12-20 17:21:59690 options.bot = presubmit_support.DoGetTrySlaves(
[email protected]f7ae6d52009-12-22 20:49:04691 checkouts[0].GetFileNames(),
[email protected]01d8c1d2010-01-07 01:56:59692 checkouts[0].checkout_root,
[email protected]ea8c1a92009-12-20 17:21:59693 root_presubmit,
694 False,
695 sys.stdout)
696 except ImportError:
697 pass
698 # If no bot is specified, either the default pool will be selected or the
699 # try server will refuse the job. Either case we don't need to interfere.
[email protected]de243452009-10-06 21:02:56700
[email protected]c9f06b02009-11-03 19:32:08701 if options.name is None:
702 if options.issue:
[email protected]e3608df2009-11-10 20:22:57703 options.name = 'Issue %s' % options.issue
[email protected]c9f06b02009-11-03 19:32:08704 else:
705 options.name = 'Unnamed'
706 print('Note: use --name NAME to change the try job name.')
707 if not options.email:
[email protected]3ccbf7e2009-12-22 20:46:42708 parser.error('Using an anonymous checkout. Please use --email or set '
709 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.')
[email protected]e1555a62009-11-09 17:05:54710 else:
711 print('Results will be emailed to: ' + options.email)
[email protected]c9f06b02009-11-03 19:32:08712
[email protected]c3057682010-05-01 01:47:02713 # Prevent rietveld updates if we aren't running all the tests.
714 if options.testfilter is not None:
715 options.issue = None
716 options.patchset = None
717
[email protected]fb2b8eb2009-04-23 21:03:42718 # Send the patch.
[email protected]b6d36492009-12-26 14:22:41719 if options.send_patch:
720 # If forced.
721 options.send_patch(options)
722 PrintSuccess(options)
723 return 0
724 try:
725 if can_http:
726 _SendChangeHTTP(options)
727 PrintSuccess(options)
728 return 0
729 except NoTryServerAccess:
730 if not can_svn:
731 raise
732 _SendChangeSVN(options)
733 PrintSuccess(options)
734 return 0
[email protected]fb2b8eb2009-04-23 21:03:42735 except (InvalidScript, NoTryServerAccess), e:
736 if swallow_exception:
[email protected]c9f06b02009-11-03 19:32:08737 return 1
[email protected]fb2b8eb2009-04-23 21:03:42738 print e
[email protected]c9f06b02009-11-03 19:32:08739 return 1
740 return 0
[email protected]fb2b8eb2009-04-23 21:03:42741
742
743if __name__ == "__main__":
[email protected]2185f002009-12-18 21:03:47744 sys.exit(TryChange(None, [], False))