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 subprocess
     20 import sys
     21 
     22 
     23 # Alias which can be used to run a try on every builder.
     24 ALL_BUILDERS = 'all'
     25 
     26 # Contact information for the build master.
     27 # TODO(borenet): Share this information from a single location. Filed bug:
     28 # http://code.google.com/p/skia/issues/detail?id=1081
     29 SKIA_BUILD_MASTER_HOST = '70.32.156.51'
     30 SKIA_BUILD_MASTER_PORT = '10117'
     31 
     32 # All try builders have this suffix.
     33 TRYBOT_SUFFIX = '_Trybot'
     34 
     35 # Location of the codereview.settings file in the Skia repo.
     36 SKIA_URL = 'skia.googlecode.com'
     37 CODEREVIEW_SETTINGS = '/svn/codereview.settings'
     38 
     39 # String for matching the svn url of the try server inside codereview.settings.
     40 TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: '
     41 
     42 # Strings used for matching svn config properties.
     43 URL_STR = 'URL: '
     44 REPO_ROOT_STR = 'Repository Root: '
     45 
     46 
     47 def FindDepotTools():
     48   """ Find depot_tools on the local machine and return its location. """
     49   which_cmd = 'where' if os.name == 'nt' else 'which'
     50   cmd = [which_cmd, 'gcl']
     51   proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     52   if proc.wait() != 0:
     53     raise Exception('Couldn\'t find depot_tools in PATH!')
     54   gcl = proc.communicate()[0].split('\n')[0].rstrip()
     55   depot_tools_dir = os.path.dirname(gcl)
     56   return depot_tools_dir
     57 
     58 
     59 def GetCheckoutRoot(is_svn=True):
     60   """ Determine where the local checkout is rooted.
     61 
     62   is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in
     63       a git checkout.
     64   """
     65   if is_svn:
     66     svn_cmd = 'svn.bat' if os.name == 'nt' else 'svn'
     67     cmd = [svn_cmd, 'info']
     68     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
     69                             stderr=subprocess.STDOUT)
     70     if proc.wait() != 0:
     71       raise Exception('Couldn\'t find checkout root!')
     72     output = proc.communicate()[0].split('\n')
     73     url = None
     74     repo_root = None
     75     for line in output:
     76       if line.startswith(REPO_ROOT_STR):
     77         repo_root = line[len(REPO_ROOT_STR):].rstrip()
     78       elif line.startswith(URL_STR):
     79         url = line[len(URL_STR):].rstrip()
     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():
    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/builders')  
    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 --bot               Builder on which to run the try. Required.
    154 -h, --help          Show this message.
    155 -r <revision#>      Revision from which to run the try.
    156 -l, --list_bots     List the available try builders and exit.
    157 """ % ('<changelist> ' if is_svn else ''))
    158 
    159   def Error(msg=None):
    160     if msg:
    161       print msg
    162     print usage
    163     sys.exit(1)
    164 
    165   using_bots = None
    166   changelist = None
    167   revision = None
    168 
    169   while argv:
    170     arg = argv.pop(0)
    171     if arg == '-h' or arg == '--help':
    172       Error()
    173     elif arg == '-l' or arg == '--list_bots':
    174       print 'submit_try: Available builders:\n  %s' % '\n  '.join(trybots)
    175       sys.exit(0)
    176     elif arg == '--bot':
    177       if using_bots:
    178         Error('--bot specified multiple times.')
    179       if len(argv) < 1:
    180         Error('You must specify a builder with "--bot".')
    181       using_bots = []
    182       while argv and not argv[0].startswith('-'):
    183         bot = argv.pop(0)
    184         if bot == ALL_BUILDERS:
    185           if using_bots:
    186             Error('Cannot specify "all" with additional builder names.')
    187           using_bots = trybots
    188           break
    189         else:
    190           if not bot in trybots:
    191             Error('Unrecognized builder: %s' % bot)
    192           using_bots.append(bot)
    193     elif arg == '-r':
    194       if len(argv) < 1:
    195         Error('You must specify a revision with "-r".')
    196       revision = argv.pop(0)
    197     else:
    198       if changelist or not is_svn:
    199         Error('Unknown argument: %s' % arg)
    200       changelist = arg
    201   if is_svn and not changelist:
    202     Error('You must specify a changelist name.')
    203   if not using_bots:
    204     Error('You must specify one or more builders using --bot.')
    205   return CollectedArgs(bots=using_bots, changelist=changelist,
    206                        revision=revision)
    207 
    208 
    209 def SubmitTryRequest(args, is_svn=True):
    210   """ Submits a try request for the given changelist on the given list of
    211   trybots.
    212 
    213   args: Object whose properties are derived from command-line arguments. If
    214       is_svn is True, it should contain:
    215       - changelist: string; the name of the changelist to try.
    216       - bot: list of strings; the names of the try builders to run.
    217       - revision: optional, int; the revision number from which to run the try.
    218       If is_svn is False, it should contain:
    219       - bot: list of strings; the names of the try builders to run.
    220       - revision: optional, int; the revision number from which to run the try. 
    221   is_svn: boolean; are we in an SVN repo?
    222   """
    223   botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots])
    224   if is_svn:
    225     gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl'
    226     try_args = [gcl_cmd, 'try', args.changelist,
    227                 '--root', GetCheckoutRoot(is_svn),
    228                 '--bot', botlist]
    229     if args.revision:
    230       try_args.extend(['-r', args.revision])
    231     print ' '.join(try_args)
    232     proc = subprocess.Popen(try_args, stdout=subprocess.PIPE,
    233                             stderr=subprocess.STDOUT)
    234     if proc.wait() != 0:
    235       raise Exception('Failed to submit try request: %s' % (
    236           proc.communicate()[0]))
    237     print proc.communicate()[0]
    238   else:
    239     # First, find depot_tools. This is needed to import trychange.
    240     sys.path.append(FindDepotTools())
    241     import trychange
    242     try_args = ['--use_svn',
    243                 '--svn_repo', GetTryRepo(),
    244                 '--root', GetCheckoutRoot(is_svn),
    245                 '--bot', botlist]
    246     if args.revision:
    247       try_args.extend(['-r', args.revision])
    248     trychange.TryChange(try_args, None, False)
    249 
    250 
    251 def main():
    252   # Retrieve the list of active try builders from the build master.
    253   trybots = RetrieveTrybotList()
    254 
    255   # Determine if we're in an SVN checkout.
    256   is_svn = os.path.isdir('.svn')
    257 
    258   # Parse and validate the command-line arguments.
    259   args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn)
    260 
    261   # Submit the try request.
    262   SubmitTryRequest(args, is_svn=is_svn)
    263 
    264 
    265 if __name__ == '__main__':
    266   sys.exit(main())