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 shutil
     21 import subprocess
     22 import svn
     23 import sys
     24 import tempfile
     25 
     26 import retrieve_from_googlesource
     27 
     28 
     29 # Alias which can be used to run a try on every builder.
     30 ALL_BUILDERS = 'all'
     31 # Alias which can be used to run a try on all compile builders.
     32 COMPILE_BUILDERS = 'compile'
     33 # Alias which can be used to run a try on all builders that are run in the CQ.
     34 CQ_BUILDERS = 'cq'
     35 # Alias which can be used to specify a regex to choose builders.
     36 REGEX = 'regex'
     37 
     38 ALL_ALIASES = [ALL_BUILDERS, COMPILE_BUILDERS, REGEX, CQ_BUILDERS]
     39 
     40 LARGE_NUMBER_OF_BOTS = 5
     41 
     42 GIT = 'git.bat' if os.name == 'nt' else 'git'
     43 
     44 # URL of the slaves.cfg file in the Skia buildbot sources.
     45 SKIA_REPO = 'https://skia.googlesource.com/buildbot'
     46 SLAVES_CFG_PATH = 'master/slaves.cfg'
     47 
     48 # All try builders have this suffix.
     49 TRYBOT_SUFFIX = '-Trybot'
     50 
     51 # String for matching the svn url of the try server inside codereview.settings.
     52 TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: '
     53 
     54 # Strings used for matching svn config properties.
     55 URL_STR = 'URL'
     56 REPO_ROOT_STR = 'Repository Root'
     57 
     58 
     59 def FindDepotTools():
     60   """ Find depot_tools on the local machine and return its location. """
     61   which_cmd = 'where' if os.name == 'nt' else 'which'
     62   cmd = [which_cmd, 'gcl']
     63   proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     64   if proc.wait() != 0:
     65     raise Exception('Couldn\'t find depot_tools in PATH!')
     66   gcl = proc.communicate()[0].split('\n')[0].rstrip()
     67   depot_tools_dir = os.path.dirname(gcl)
     68   return depot_tools_dir
     69 
     70 
     71 def GetCheckoutRoot():
     72   """ Determine where the local checkout is rooted."""
     73   cmd = ['git', 'rev-parse', '--show-toplevel']
     74   proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
     75                           stderr=subprocess.STDOUT)
     76   if proc.wait() != 0:
     77     raise Exception('Couldn\'t find checkout root!')
     78   return os.path.basename(proc.communicate()[0])
     79 
     80 
     81 def GetTryRepo():
     82   """Determine the TRYSERVER_SVN_URL from the codereview.settings file."""
     83   codereview_settings_file = os.path.join(os.path.dirname(__file__), os.pardir,
     84                                           'codereview.settings')
     85   with open(codereview_settings_file) as f:
     86     for line in f:
     87       if line.startswith(TRYSERVER_SVN_URL):
     88         return line[len(TRYSERVER_SVN_URL):].rstrip()
     89   raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is '
     90                   'defined in the %s file.' % codereview_settings_file)
     91 
     92 
     93 def RetrieveTrybotList():
     94   """Retrieve the list of known trybots from the checked-in buildbot
     95   configuration."""
     96   # Retrieve the slaves.cfg file from the repository.
     97   slaves_cfg_text = retrieve_from_googlesource.get(SKIA_REPO, SLAVES_CFG_PATH)
     98 
     99   # Execute the slaves.cfg file to obtain the list of slaves.
    100   vars = {}
    101   exec(slaves_cfg_text, vars)
    102   slaves_cfg = vars['slaves']
    103 
    104   # Pull the list of known builders from the slaves list.
    105   trybots = set()
    106   for slave in slaves_cfg:
    107     for builder in slave['builder']:
    108       if not builder.endswith(TRYBOT_SUFFIX):
    109         trybots.add(builder)
    110 
    111   return list(trybots), vars['cq_trybots']
    112 
    113 
    114 def ValidateArgs(argv, trybots, cq_trybots, is_svn=True):
    115   """ Parse and validate command-line arguments. If the arguments are valid,
    116   returns a tuple of (<changelist name>, <list of trybots>).
    117 
    118   trybots: list of strings; A list of the known try builders.
    119   cq_trybots: list of strings; Trybots who get run by the commit queue.
    120   is_svn: bool; whether or not we're in an svn checkout.
    121   """
    122 
    123   class CollectedArgs(object):
    124     def __init__(self, bots, changelist, revision):
    125       self._bots = bots
    126       self._changelist = changelist
    127       self._revision = revision
    128 
    129     @property
    130     def bots(self):
    131       for bot in self._bots:
    132         yield bot
    133 
    134     @property
    135     def changelist(self):
    136       return self._changelist
    137 
    138     @property
    139     def revision(self):
    140       return self._revision
    141 
    142   usage = (
    143 """submit_try: Submit a try request.
    144 submit_try %s--bot <buildername> [<buildername> ...]
    145 
    146 -b, --bot           Builder(s) or Alias on which to run the try. Required.
    147                     Allowed aliases: %s
    148 -h, --help          Show this message.
    149 -r <revision#>      Revision from which to run the try.
    150 -l, --list_bots     List the available try builders and aliases and exit.
    151 """ % ('<changelist> ' if is_svn else '', ALL_ALIASES))
    152 
    153   def Error(msg=None):
    154     if msg:
    155       print msg
    156     print usage
    157     sys.exit(1)
    158 
    159   using_bots = None
    160   changelist = None
    161   revision = None
    162 
    163   while argv:
    164     arg = argv.pop(0)
    165     if arg == '-h' or arg == '--help':
    166       Error()
    167     elif arg == '-l' or arg == '--list_bots':
    168       format_args = ['\n  '.join(sorted(trybots))] + \
    169                     ALL_ALIASES + \
    170                     ['\n    '.join(sorted(cq_trybots))]
    171       print (
    172 """
    173 submit_try: Available builders:\n  %s
    174 
    175 Can also use the following aliases to run on groups of builders-
    176   %s: Will run against all trybots.
    177   %s: Will run against all compile trybots.
    178   %s: You will be prompted to enter a regex to select builders with.
    179   %s: Will run against the same trybots as the commit queue:\n    %s
    180 
    181 """ % tuple(format_args))
    182       sys.exit(0)
    183     elif arg == '-b' or arg == '--bot':
    184       if using_bots:
    185         Error('--bot specified multiple times.')
    186       if len(argv) < 1:
    187         Error('You must specify a builder with "--bot".')
    188       using_bots = []
    189       while argv and not argv[0].startswith('-'):
    190         for bot in argv.pop(0).split(','):
    191           if bot in ALL_ALIASES:
    192             if using_bots:
    193               Error('Cannot specify "%s" with additional builder names or '
    194                     'aliases.' % bot)
    195             elif bot == COMPILE_BUILDERS:
    196               using_bots = [t for t in trybots if t.startswith('Build')]
    197             elif bot == CQ_BUILDERS:
    198               using_bots = cq_trybots
    199             elif bot == REGEX:
    200               while True:
    201                 regex = raw_input("Enter your trybot regex: ")
    202                 p = re.compile(regex)
    203                 using_bots = [t for t in trybots if p.match(t)]
    204                 print '\n\nTrybots that match your regex:\n%s\n\n' % '\n'.join(
    205                     using_bots)
    206                 if raw_input('Re-enter regex? [y,n]: ') == 'n':
    207                   break
    208             break
    209           else:
    210             if not bot in trybots:
    211               Error('Unrecognized builder: %s' % bot)
    212             using_bots.append(bot)
    213     elif arg == '-r':
    214       if len(argv) < 1:
    215         Error('You must specify a revision with "-r".')
    216       revision = argv.pop(0)
    217     else:
    218       if changelist or not is_svn:
    219         Error('Unknown argument: %s' % arg)
    220       changelist = arg
    221   if is_svn and not changelist:
    222     Error('You must specify a changelist name.')
    223   if not using_bots:
    224     Error('You must specify one or more builders using --bot.')
    225   if len(using_bots) > LARGE_NUMBER_OF_BOTS:
    226     are_you_sure = raw_input('Running a try on a large number of bots is very '
    227                              'expensive. You may be able to get enough '
    228                              'information by running on a smaller set of bots. '
    229                              'Are you sure you want to do this? [y,n]: ')
    230     if are_you_sure != 'y':
    231       Error()
    232   return CollectedArgs(bots=using_bots, changelist=changelist,
    233                        revision=revision)
    234 
    235 
    236 def SubmitTryRequest(trybots, revision=None):
    237   """ Submits a try request on the given list of trybots.
    238 
    239   Args:
    240       trybots: list of strings; the names of the try builders to run.
    241       revision: optional string; the revision from which to run the try.
    242   """
    243   botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in trybots])
    244   # Find depot_tools. This is needed to import git_cl and trychange.
    245   sys.path.append(FindDepotTools())
    246   import git_cl
    247   import trychange
    248 
    249   cmd = [GIT, 'diff', git_cl.Changelist().GetUpstreamBranch(),
    250          '--no-ext-diff']
    251   proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    252   git_data = proc.communicate()
    253   if git_data[0] is None:
    254     raise Exception('Failed to capture git diff!')
    255 
    256   temp_dir = tempfile.mkdtemp()
    257   try:
    258     diff_file = os.path.join(temp_dir, 'patch.diff')
    259     with open(diff_file, 'wb') as f:
    260       f.write(git_data[0])
    261       f.close()
    262 
    263     try_args = ['--use_svn',
    264                 '--svn_repo', GetTryRepo(),
    265                 '--root', GetCheckoutRoot(),
    266                 '--bot', botlist,
    267                 '--diff', diff_file,
    268                 ]
    269     if revision:
    270       try_args.extend(['-r', revision])
    271 
    272     # Submit the try request.
    273     trychange.TryChange(try_args, None, False)
    274   finally:
    275     shutil.rmtree(temp_dir)
    276 
    277 
    278 def main():
    279   # Retrieve the list of active try builders from the build master.
    280   trybots, cq_trybots = RetrieveTrybotList()
    281 
    282   # Determine if we're in an SVN checkout.
    283   is_svn = os.path.isdir('.svn')
    284 
    285   # Parse and validate the command-line arguments.
    286   args = ValidateArgs(sys.argv[1:], trybots=trybots, cq_trybots=cq_trybots,
    287                       is_svn=is_svn)
    288 
    289   # Submit the try request.
    290   SubmitTryRequest(args.bots, args.revision)
    291 
    292 
    293 if __name__ == '__main__':
    294   sys.exit(main())
    295