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