• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
9submit_try: Submit a try request.
10
11This is a thin wrapper around the try request utilities in depot_tools which
12adds some validation and supports both git and svn.
13"""
14
15
16import httplib
17import json
18import os
19import re
20import subprocess
21import svn
22import sys
23
24import buildbot_globals
25
26
27# Alias which can be used to run a try on every builder.
28ALL_BUILDERS = 'all'
29# Alias which can be used to run a try on all compile builders.
30COMPILE_BUILDERS = 'compile'
31# Alias which can be used to run a try on all builders that are run in the CQ.
32CQ_BUILDERS = 'cq'
33# Alias which can be used to specify a regex to choose builders.
34REGEX = 'regex'
35
36ALL_ALIASES = [ALL_BUILDERS, COMPILE_BUILDERS, CQ_BUILDERS, REGEX]
37
38# Contact information for the build master.
39SKIA_BUILD_MASTER_HOST = str(buildbot_globals.Get('public_master_host'))
40SKIA_BUILD_MASTER_PORT = str(buildbot_globals.Get('public_external_port'))
41
42# All try builders have this suffix.
43TRYBOT_SUFFIX = '-Trybot'
44
45# Location of the codereview.settings file in the Skia repo.
46SKIA_URL = 'skia.googlecode.com'
47CODEREVIEW_SETTINGS = '/svn/codereview.settings'
48
49# String for matching the svn url of the try server inside codereview.settings.
50TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: '
51
52# Strings used for matching svn config properties.
53URL_STR = 'URL'
54REPO_ROOT_STR = 'Repository Root'
55
56
57def 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
69def 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
94def 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
107def 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
123def 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.
151submit_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"""
178submit_try: Available builders:\n  %s
179
180Can 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
243def 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
286def 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
300if __name__ == '__main__':
301  sys.exit(main())
302