Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(227)

Side by Side Diff: trychange.py

Issue 503068: Make breakpad, gcl and presubmit_support dependencies optional (Closed)
Patch Set: Fix spurious error Created 11 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tests/trychange_unittest.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 """Client-side script to send a try job to the try server. It communicates to 5 """Client-side script to send a try job to the try server. It communicates to
6 the try server by either writting to a svn repository or by directly connecting 6 the try server by either writting to a svn repository or by directly connecting
7 to the server by HTTP. 7 to the server by HTTP.
8 """ 8 """
9 9
10 import datetime 10 import datetime
11 import getpass 11 import getpass
12 import logging 12 import logging
13 import optparse 13 import optparse
14 import os 14 import os
15 import shutil 15 import shutil
16 import socket 16 import socket
17 import subprocess 17 import subprocess
18 import sys 18 import sys
19 import tempfile 19 import tempfile
20 import urllib 20 import urllib
21 21
22 import breakpad 22 try:
23 import breakpad
24 except ImportError:
25 pass
23 26
24 import gcl
25 import gclient_utils 27 import gclient_utils
26 import scm 28 import scm
27 import presubmit_support
28 29
29 __version__ = '1.2' 30 __version__ = '1.2'
30 31
31 32
32 # Constants 33 # Constants
33 HELP_STRING = "Sorry, Tryserver is not available." 34 HELP_STRING = "Sorry, Tryserver is not available."
34 USAGE = r"""%prog [change_name] [options] 35 USAGE = r"""%prog [change_name] [options]
35 36
36 Client-side script to send a try job to the try server. It communicates to 37 Client-side script to send a try job to the try server. It communicates to
37 the try server by either writting to a svn repository or by directly connecting 38 the try server by either writting to a svn repository or by directly connecting
(...skipping 20 matching lines...) Expand all
58 class InvalidScript(Exception): 59 class InvalidScript(Exception):
59 def __str__(self): 60 def __str__(self):
60 return self.args[0] + '\n' + HELP_STRING 61 return self.args[0] + '\n' + HELP_STRING
61 62
62 63
63 class NoTryServerAccess(Exception): 64 class NoTryServerAccess(Exception):
64 def __str__(self): 65 def __str__(self):
65 return self.args[0] + '\n' + HELP_STRING 66 return self.args[0] + '\n' + HELP_STRING
66 67
67 68
68 def GetTryServerSettings():
69 """Grab try server settings local to the repository."""
70 def _SafeResolve(host):
71 try:
72 return socket.getaddrinfo(host, None)
73 except socket.gaierror:
74 return None
75
76 settings = {}
77 settings['http_port'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_PORT')
78 settings['http_host'] = gcl.GetCodeReviewSetting('TRYSERVER_HTTP_HOST')
79 settings['svn_repo'] = gcl.GetCodeReviewSetting('TRYSERVER_SVN_URL')
80 settings['default_project'] = gcl.GetCodeReviewSetting('TRYSERVER_PROJECT')
81 settings['default_root'] = gcl.GetCodeReviewSetting('TRYSERVER_ROOT')
82
83 # Pick a patchlevel, default to 0.
84 default_patchlevel = gcl.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL')
85 if default_patchlevel:
86 default_patchlevel = int(default_patchlevel)
87 else:
88 default_patchlevel = 0
89 settings['default_patchlevel'] = default_patchlevel
90
91 # Use http is the http_host name resolve, fallback to svn otherwise.
92 if (settings['http_port'] and settings['http_host'] and
93 _SafeResolve(settings['http_host'])):
94 settings['default_transport'] = 'http'
95 elif settings.get('svn_repo'):
96 settings['default_transport'] = 'svn'
97 return settings
98
99
100 def EscapeDot(name): 69 def EscapeDot(name):
101 return name.replace('.', '-') 70 return name.replace('.', '-')
102 71
103 72
104 class SCM(object): 73 class SCM(object):
105 """Simplistic base class to implement one function: ProcessOptions.""" 74 """Simplistic base class to implement one function: ProcessOptions."""
106 def __init__(self, options): 75 def __init__(self, options):
107 self.options = options 76 self.options = options
108 77
109 def GetFileNames(self): 78 def GetFileNames(self):
(...skipping 26 matching lines...) Expand all
136 self.options.files = [f[1] for f in scm.SVN.CaptureStatus(None)] 105 self.options.files = [f[1] for f in scm.SVN.CaptureStatus(None)]
137 # Directories will return None so filter them out. 106 # Directories will return None so filter them out.
138 diff = filter(None, [scm.SVN.DiffItem(f) for f in self.options.files]) 107 diff = filter(None, [scm.SVN.DiffItem(f) for f in self.options.files])
139 os.chdir(previous_cwd) 108 os.chdir(previous_cwd)
140 return "".join(diff) 109 return "".join(diff)
141 110
142 def GetLocalRoot(self): 111 def GetLocalRoot(self):
143 """Return the path of the repository root.""" 112 """Return the path of the repository root."""
144 return self.checkout_root 113 return self.checkout_root
145 114
115 def GetBots(self):
116 try:
117 import gcl
118 return gcl.GetCachedFile('PRESUBMIT.py', use_root=True)
119 except ImportError:
120 try:
121 return gclient_utils.FileRead(os.path.join(self.checkout_root,
122 'PRESUBMIT.py'))
123 except OSError:
124 return None
125
146 126
147 class GIT(SCM): 127 class GIT(SCM):
148 """Gathers the options and diff for a git checkout.""" 128 """Gathers the options and diff for a git checkout."""
149 def __init__(self, *args, **kwargs): 129 def __init__(self, *args, **kwargs):
150 SCM.__init__(self, *args, **kwargs) 130 SCM.__init__(self, *args, **kwargs)
151 self.checkout_root = os.path.abspath( 131 self.checkout_root = os.path.abspath(
152 gclient_utils.CheckCall(['git', 'rev-parse', '--show-cdup']).strip()) 132 gclient_utils.CheckCall(['git', 'rev-parse', '--show-cdup']).strip())
153 if not self.options.diff: 133 if not self.options.diff:
154 self.options.diff = self._GenerateDiff() 134 self.options.diff = self._GenerateDiff()
155 if not self.options.name: 135 if not self.options.name:
(...skipping 21 matching lines...) Expand all
177 if not branch.startswith('refs/heads/'): 157 if not branch.startswith('refs/heads/'):
178 # TODO(maruel): Find a better type. 158 # TODO(maruel): Find a better type.
179 raise NoTryServerAccess("Couldn't figure out branch name") 159 raise NoTryServerAccess("Couldn't figure out branch name")
180 branch = branch[len('refs/heads/'):] 160 branch = branch[len('refs/heads/'):]
181 return branch 161 return branch
182 162
183 def GetLocalRoot(self): 163 def GetLocalRoot(self):
184 """Return the path of the repository root.""" 164 """Return the path of the repository root."""
185 return self.checkout_root 165 return self.checkout_root
186 166
167 def GetBots(self):
168 try:
169 return gclient_utils.FileRead(os.path.join(self.checkout_root,
170 'PRESUBMIT.py'))
171 except OSError:
172 return None
173
187 174
188 def _ParseSendChangeOptions(options): 175 def _ParseSendChangeOptions(options):
189 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" 176 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN."""
190 values = {} 177 values = {}
191 if options.email: 178 if options.email:
192 values['email'] = options.email 179 values['email'] = options.email
193 values['user'] = options.user 180 values['user'] = options.user
194 values['name'] = options.name 181 values['name'] = options.name
195 if options.bot: 182 if options.bot:
196 values['bot'] = ','.join(options.bot) 183 values['bot'] = ','.join(options.bot)
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
263 description += "%s=%s\n" % (k,v) 250 description += "%s=%s\n" % (k,v)
264 251
265 # Do an empty checkout. 252 # Do an empty checkout.
266 temp_dir = tempfile.mkdtemp() 253 temp_dir = tempfile.mkdtemp()
267 temp_file = tempfile.NamedTemporaryFile() 254 temp_file = tempfile.NamedTemporaryFile()
268 try: 255 try:
269 try: 256 try:
270 command = ['svn', 'checkout', '--depth', 'empty', '-q', 257 command = ['svn', 'checkout', '--depth', 'empty', '-q',
271 options.svn_repo, temp_dir] 258 options.svn_repo, temp_dir]
272 if options.email: 259 if options.email:
273 command += ['--username', options.email] 260 command.extend(['--username', options.email])
274 gclient_utils.CheckCall(command) 261 gclient_utils.CheckCall(command)
275 262
276 # TODO(maruel): Use a subdirectory per user? 263 # TODO(maruel): Use a subdirectory per user?
277 current_time = str(datetime.datetime.now()).replace(':', '.') 264 current_time = str(datetime.datetime.now()).replace(':', '.')
278 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) + 265 file_name = (EscapeDot(options.user) + '.' + EscapeDot(options.name) +
279 '.%s.diff' % current_time) 266 '.%s.diff' % current_time)
280 full_path = os.path.join(temp_dir, file_name) 267 full_path = os.path.join(temp_dir, file_name)
281 full_url = options.svn_repo + '/' + file_name 268 full_url = options.svn_repo + '/' + file_name
282 file_found = False 269 file_found = False
283 try: 270 try:
284 gclient_utils.CheckCall(['svn', 'ls', full_url]) 271 gclient_utils.CheckCall(['svn', 'ls', full_url], print_error=False)
285 file_found = True 272 file_found = True
286 except gclient_utils.CheckCallError: 273 except gclient_utils.CheckCallError:
287 pass 274 pass
288 if file_found: 275 if file_found:
289 # The file already exists in the repo. Note that commiting a file is a 276 # The file already exists in the repo. Note that commiting a file is a
290 # no-op if the file's content (the diff) is not modified. This is why 277 # no-op if the file's content (the diff) is not modified. This is why
291 # the file name contains the date and time. 278 # the file name contains the date and time.
292 gclient_utils.CheckCall(['svn', 'update', full_path]) 279 gclient_utils.CheckCall(['svn', 'update', full_path],
293 f = open(full_path, 'wb') 280 print_error=False)
294 f.write(options.diff) 281 gclient_utils.FileWrite(full_path, options.diff, 'wb')
295 f.close()
296 else: 282 else:
297 # Add the file to the repo 283 # Add the file to the repo.
298 f = open(full_path, 'wb') 284 gclient_utils.FileWrite(full_path, options.diff, 'wb')
299 f.write(options.diff) 285 gclient_utils.CheckCall(["svn", "add", full_path], print_error=False)
300 f.close()
301 gclient_utils.CheckCall(["svn", "add", full_path])
302 temp_file.write(description) 286 temp_file.write(description)
303 temp_file.flush() 287 temp_file.flush()
304 gclient_utils.CheckCall(["svn", "commit", full_path, '--file', 288 gclient_utils.CheckCall(["svn", "commit", full_path, '--file',
305 temp_file.name]) 289 temp_file.name], print_error=False)
306 except gclient_utils.CheckCallError, e: 290 except gclient_utils.CheckCallError, e:
307 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' + 291 raise NoTryServerAccess(' '.join(e.command) + '\nOuput:\n' +
308 e.stdout) 292 e.stdout)
309 finally: 293 finally:
310 temp_file.close() 294 temp_file.close()
311 shutil.rmtree(temp_dir, True) 295 shutil.rmtree(temp_dir, True)
312 296
313 297
314 def GuessVCS(options): 298 def GuessVCS(options):
315 """Helper to guess the version control system. 299 """Helper to guess the version control system.
(...skipping 30 matching lines...) Expand all
346 def TryChange(argv, 330 def TryChange(argv,
347 file_list, 331 file_list,
348 swallow_exception, 332 swallow_exception,
349 prog=None): 333 prog=None):
350 """ 334 """
351 Args: 335 Args:
352 argv: Arguments and options. 336 argv: Arguments and options.
353 file_list: Default value to pass to --file. 337 file_list: Default value to pass to --file.
354 swallow_exception: Whether we raise or swallow exceptions. 338 swallow_exception: Whether we raise or swallow exceptions.
355 """ 339 """
356 default_settings = GetTryServerSettings()
357 transport_functions = { 'http': _SendChangeHTTP, 'svn': _SendChangeSVN }
358 default_transport = transport_functions.get(
359 default_settings.get('default_transport'))
360
361 # Parse argv 340 # Parse argv
362 parser = optparse.OptionParser(usage=USAGE, 341 parser = optparse.OptionParser(usage=USAGE,
363 version=__version__, 342 version=__version__,
364 prog=prog) 343 prog=prog)
365 344
366 group = optparse.OptionGroup(parser, "Result and status") 345 group = optparse.OptionGroup(parser, "Result and status")
367 group.add_option("-u", "--user", default=getpass.getuser(), 346 group.add_option("-u", "--user", default=getpass.getuser(),
368 help="Owner user name [default: %default]") 347 help="Owner user name [default: %default]")
369 group.add_option("-e", "--email", 348 group.add_option("-e", "--email",
370 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS', 349 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
(...skipping 19 matching lines...) Expand all
390 help="Revision to use for the try job; default: the " 369 help="Revision to use for the try job; default: the "
391 "revision will be determined by the try server; see " 370 "revision will be determined by the try server; see "
392 "its waterfall for more info") 371 "its waterfall for more info")
393 group.add_option("-c", "--clobber", action="store_true", 372 group.add_option("-c", "--clobber", action="store_true",
394 help="Force a clobber before building; e.g. don't do an " 373 help="Force a clobber before building; e.g. don't do an "
395 "incremental build") 374 "incremental build")
396 # TODO(maruel): help="Select a specific configuration, usually 'debug' or " 375 # TODO(maruel): help="Select a specific configuration, usually 'debug' or "
397 # "'release'" 376 # "'release'"
398 group.add_option("--target", help=optparse.SUPPRESS_HELP) 377 group.add_option("--target", help=optparse.SUPPRESS_HELP)
399 378
400 # TODO(bradnelson): help="Override which project to use" 379 group.add_option("--project",
401 group.add_option("--project", help=optparse.SUPPRESS_HELP, 380 help="Override which project to use")
402 default=default_settings['default_project'])
403 381
404 # Override the list of tests to run, use multiple times to list many tests 382 # Override the list of tests to run, use multiple times to list many tests
405 # (or comma separated) 383 # (or comma separated)
406 group.add_option("-t", "--tests", action="append", 384 group.add_option("-t", "--tests", action="append",
407 help=optparse.SUPPRESS_HELP) 385 help=optparse.SUPPRESS_HELP)
408 parser.add_option_group(group) 386 parser.add_option_group(group)
409 387
410 group = optparse.OptionGroup(parser, "Patch to run") 388 group = optparse.OptionGroup(parser, "Patch to run")
411 group.add_option("-f", "--file", default=file_list, dest="files", 389 group.add_option("-f", "--file", default=file_list, dest="files",
412 metavar="FILE", action="append", 390 metavar="FILE", action="append",
413 help="Use many times to list the files to include in the " 391 help="Use many times to list the files to include in the "
414 "try, relative to the repository root") 392 "try, relative to the repository root")
415 group.add_option("--diff", 393 group.add_option("--diff",
416 help="File containing the diff to try") 394 help="File containing the diff to try")
417 group.add_option("--url", 395 group.add_option("--url",
418 help="Url where to grab a patch") 396 help="Url where to grab a patch")
419 group.add_option("--root", 397 group.add_option("--root",
420 help="Root to use for the patch; base subdirectory for " 398 help="Root to use for the patch; base subdirectory for "
421 "patch created in a subdirectory", 399 "patch created in a subdirectory")
422 default=default_settings["default_root"])
423 group.add_option("--patchlevel", type='int', metavar="LEVEL", 400 group.add_option("--patchlevel", type='int', metavar="LEVEL",
424 help="Used as -pN parameter to patch", 401 help="Used as -pN parameter to patch")
425 default=default_settings["default_patchlevel"])
426 parser.add_option_group(group) 402 parser.add_option_group(group)
427 403
428 group = optparse.OptionGroup(parser, "Access the try server by HTTP") 404 group = optparse.OptionGroup(parser, "Access the try server by HTTP")
429 group.add_option("--use_http", 405 group.add_option("--use_http",
430 action="store_const", 406 action="store_const",
431 const=_SendChangeHTTP, 407 const=_SendChangeHTTP,
432 dest="send_patch", 408 dest="send_patch",
433 default=default_transport,
434 help="Use HTTP to talk to the try server [default]") 409 help="Use HTTP to talk to the try server [default]")
435 group.add_option("--host", 410 group.add_option("--host",
436 default=default_settings['http_host'],
437 help="Host address") 411 help="Host address")
438 group.add_option("--port", 412 group.add_option("--port",
439 default=default_settings['http_port'],
440 help="HTTP port") 413 help="HTTP port")
441 group.add_option("--proxy", 414 group.add_option("--proxy",
442 help="HTTP proxy") 415 help="HTTP proxy")
443 parser.add_option_group(group) 416 parser.add_option_group(group)
444 417
445 group = optparse.OptionGroup(parser, "Access the try server with SVN") 418 group = optparse.OptionGroup(parser, "Access the try server with SVN")
446 group.add_option("--use_svn", 419 group.add_option("--use_svn",
447 action="store_const", 420 action="store_const",
448 const=_SendChangeSVN, 421 const=_SendChangeSVN,
449 dest="send_patch", 422 dest="send_patch",
450 help="Use SVN to talk to the try server") 423 help="Use SVN to talk to the try server")
451 group.add_option("--svn_repo", 424 group.add_option("--svn_repo",
452 metavar="SVN_URL", 425 metavar="SVN_URL",
453 default=default_settings['svn_repo'],
454 help="SVN url to use to write the changes in; --use_svn is " 426 help="SVN url to use to write the changes in; --use_svn is "
455 "implied when using --svn_repo") 427 "implied when using --svn_repo")
456 parser.add_option_group(group) 428 parser.add_option_group(group)
457 429
458 options, args = parser.parse_args(argv) 430 options, args = parser.parse_args(argv)
459 431
460 # Switch the default accordingly if there was no default send_patch. 432 # Switch the default accordingly if there was no default send_patch.
461 if not options.send_patch: 433 if not options.send_patch:
462 if options.port and options.host: 434 if options.port and options.host:
463 options.send_patch = _SendChangeHTTP 435 options.send_patch = _SendChangeHTTP
(...skipping 20 matching lines...) Expand all
484 try: 456 try:
485 options.scm = GuessVCS(options) 457 options.scm = GuessVCS(options)
486 except NoTryServerAccess, e: 458 except NoTryServerAccess, e:
487 # If we got the diff, we don't care. 459 # If we got the diff, we don't care.
488 if not options.diff: 460 if not options.diff:
489 # TODO(maruel): Raise what? 461 # TODO(maruel): Raise what?
490 raise 462 raise
491 463
492 # Get try slaves from PRESUBMIT.py files if not specified. 464 # Get try slaves from PRESUBMIT.py files if not specified.
493 if not options.bot: 465 if not options.bot:
494 if options.url: 466 # Even if the diff comes from options.url, use the local checkout for bot
495 parser.error('You need to specify which bots to use.') 467 # selection.
496 root_presubmit = gcl.GetCachedFile('PRESUBMIT.py', use_root=True) 468 try:
497 options.bot = presubmit_support.DoGetTrySlaves(options.scm.GetFileNames(), 469 # Get try slaves from PRESUBMIT.py files if not specified.
498 options.scm.GetLocalRoot(), 470 import presubmit_support
499 root_presubmit, 471 root_presubmit = options.scm.GetBots()
500 False, 472 options.bot = presubmit_support.DoGetTrySlaves(
501 sys.stdout) 473 options.scm.GetFileNames(),
474 options.scm.GetLocalRoot(),
475 root_presubmit,
476 False,
477 sys.stdout)
478 except ImportError:
479 pass
480 # If no bot is specified, either the default pool will be selected or the
481 # try server will refuse the job. Either case we don't need to interfere.
502 482
503 if options.name is None: 483 if options.name is None:
504 if options.issue: 484 if options.issue:
505 options.name = 'Issue %s' % options.issue 485 options.name = 'Issue %s' % options.issue
506 else: 486 else:
507 options.name = 'Unnamed' 487 options.name = 'Unnamed'
508 print('Note: use --name NAME to change the try job name.') 488 print('Note: use --name NAME to change the try job name.')
509 if not options.email: 489 if not options.email:
510 print('Warning: TRYBOT_RESULTS_EMAIL_ADDRESS is not set. Try server ' 490 print('Warning: TRYBOT_RESULTS_EMAIL_ADDRESS is not set. Try server '
511 'results might\ngo to: %s@google.com.\n' % options.user) 491 'results might\ngo to: %s@google.com.\n' % options.user)
512 else: 492 else:
513 print('Results will be emailed to: ' + options.email) 493 print('Results will be emailed to: ' + options.email)
514 494
515 # Send the patch. 495 # Send the patch.
516 options.send_patch(options) 496 options.send_patch(options)
517 print 'Patch \'%s\' sent to try server: %s' % (options.name, 497 print 'Patch \'%s\' sent to try server: %s' % (options.name,
518 ', '.join(options.bot)) 498 ', '.join(options.bot))
519 except (InvalidScript, NoTryServerAccess), e: 499 except (InvalidScript, NoTryServerAccess), e:
520 if swallow_exception: 500 if swallow_exception:
521 return 1 501 return 1
522 print e 502 print e
523 return 1 503 return 1
524 return 0 504 return 0
525 505
526 506
527 if __name__ == "__main__": 507 if __name__ == "__main__":
528 sys.exit(TryChange(None, [], False)) 508 sys.exit(TryChange(None, [], False))
OLDNEW
« no previous file with comments | « tests/trychange_unittest.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698