• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2
2
3# Copyright 2014 Google Inc.
4#
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8"""Skia's Chromium DEPS roll script.
9
10This script:
11- searches through the last N Skia git commits to find out the hash that is
12  associated with the SVN revision number.
13- creates a new branch in the Chromium tree, modifies the DEPS file to
14  point at the given Skia commit, commits, uploads to Rietveld, and
15  deletes the local copy of the branch.
16- creates a whitespace-only commit and uploads that to to Rietveld.
17- returns the Chromium tree to its previous state.
18
19To specify the location of the git executable, set the GIT_EXECUTABLE
20environment variable.
21
22Usage:
23  %prog -c CHROMIUM_PATH -r REVISION [OPTIONAL_OPTIONS]
24"""
25
26
27import optparse
28import os
29import re
30import shutil
31import sys
32import tempfile
33
34import fix_pythonpath # pylint: disable=W0611
35from common.py.utils import git_utils
36from common.py.utils import misc
37from common.py.utils import shell_utils
38
39
40DEFAULT_BOTS_LIST = [
41  'android_clang_dbg',
42  'android_dbg',
43  'android_rel',
44  'cros_daisy',
45  'linux',
46  'linux_asan',
47  'linux_chromeos',
48  'linux_chromeos_asan',
49  'linux_chromium_gn_dbg',
50  'linux_gpu',
51  'linux_layout',
52  'linux_layout_rel',
53  'mac',
54  'mac_asan',
55  'mac_gpu',
56  'mac_layout',
57  'mac_layout_rel',
58  'win',
59  'win_gpu',
60  'win_layout',
61  'win_layout_rel',
62]
63
64REGEXP_SKIA_REVISION = (
65    r'^  "skia_revision": "(?P<revision>[0-9a-fA-F]{2,40})",$')
66
67
68class DepsRollConfig(object):
69  """Contains configuration options for this module.
70
71  Attributes:
72      chromium_path: (string) path to a local chromium git repository.
73      save_branches: (boolean) iff false, delete temporary branches.
74      verbose: (boolean)  iff false, suppress the output from git-cl.
75      skip_cl_upload: (boolean)
76      cl_bot_list: (list of strings)
77  """
78
79  # pylint: disable=I0011,R0903,R0902
80  def __init__(self, options=None):
81    if not options:
82      options = DepsRollConfig.GetOptionParser()
83    # pylint: disable=I0011,E1103
84    self.verbose = options.verbose
85    self.save_branches = not options.delete_branches
86    self.chromium_path = options.chromium_path
87    self.skip_cl_upload = options.skip_cl_upload
88    # Split and remove empty strigns from the bot list.
89    self.cl_bot_list = [bot for bot in options.bots.split(',') if bot]
90    self.default_branch_name = 'autogenerated_deps_roll_branch'
91    self.reviewers_list = ','.join([
92        # 'rmistry@google.com',
93        # 'reed@google.com',
94        # 'bsalomon@google.com',
95        # 'robertphillips@google.com',
96        ])
97    self.cc_list = ','.join([
98        # 'skia-team@google.com',
99        ])
100
101  @staticmethod
102  def GetOptionParser():
103    # pylint: disable=I0011,C0103
104    """Returns an optparse.OptionParser object.
105
106    Returns:
107        An optparse.OptionParser object.
108
109    Called by the main() function.
110    """
111    option_parser = optparse.OptionParser(usage=__doc__)
112    # Anyone using this script on a regular basis should set the
113    # CHROMIUM_CHECKOUT_PATH environment variable.
114    option_parser.add_option(
115        '-c', '--chromium_path', help='Path to local Chromium Git'
116        ' repository checkout, defaults to CHROMIUM_CHECKOUT_PATH'
117        ' if that environment variable is set.',
118        default=os.environ.get('CHROMIUM_CHECKOUT_PATH'))
119    option_parser.add_option(
120        '-r', '--revision', default=None,
121        help='The Skia Git commit hash.')
122
123    option_parser.add_option(
124        '', '--delete_branches', help='Delete the temporary branches',
125        action='store_true', dest='delete_branches', default=False)
126    option_parser.add_option(
127        '', '--verbose', help='Do not suppress the output from `git cl`.',
128        action='store_true', dest='verbose', default=False)
129    option_parser.add_option(
130        '', '--skip_cl_upload', help='Skip the cl upload step; useful'
131        ' for testing.',
132        action='store_true', default=False)
133
134    default_bots_help = (
135        'Comma-separated list of bots, defaults to a list of %d bots.'
136        '  To skip `git cl try`, set this to an empty string.'
137        % len(DEFAULT_BOTS_LIST))
138    default_bots = ','.join(DEFAULT_BOTS_LIST)
139    option_parser.add_option(
140        '', '--bots', help=default_bots_help, default=default_bots)
141
142    return option_parser
143
144
145class DepsRollError(Exception):
146  """Exceptions specific to this module."""
147  pass
148
149
150def change_skia_deps(revision, depspath):
151  """Update the DEPS file.
152
153  Modify the skia_revision entry in the given DEPS file.
154
155  Args:
156      revision: (string) Skia commit hash.
157      depspath: (string) path to DEPS file.
158  """
159  temp_file = tempfile.NamedTemporaryFile(delete=False,
160                                          prefix='skia_DEPS_ROLL_tmp_')
161  try:
162    deps_regex_rev = re.compile(REGEXP_SKIA_REVISION)
163    deps_regex_rev_repl = '  "skia_revision": "%s",' % revision
164
165    with open(depspath, 'r') as input_stream:
166      for line in input_stream:
167        line = deps_regex_rev.sub(deps_regex_rev_repl, line)
168        temp_file.write(line)
169  finally:
170    temp_file.close()
171  shutil.move(temp_file.name, depspath)
172
173
174def submit_tries(bots_to_run, dry_run=False):
175  """Submit try requests for the current branch on the given bots.
176
177  Args:
178      bots_to_run: (list of strings) bots to request.
179      dry_run: (bool) whether to actually submit the try request.
180  """
181  git_try = [
182      git_utils.GIT, 'cl', 'try', '-m', 'tryserver.chromium']
183  git_try.extend([arg for bot in bots_to_run for arg in ('-b', bot)])
184
185  if dry_run:
186    space = '   '
187    print 'You should call:'
188    print space, git_try
189    print
190  else:
191    shell_utils.run(git_try)
192
193
194def roll_deps(config, revision):
195  """Upload changed DEPS and a whitespace change.
196
197  Given the correct git_hash, create two Reitveld issues.
198
199  Args:
200      config: (roll_deps.DepsRollConfig) object containing options.
201      revision: (string) Skia Git hash.
202
203  Returns:
204      a tuple containing textual description of the two issues.
205
206  Raises:
207      OSError: failed to execute git or git-cl.
208      subprocess.CalledProcessError: git returned unexpected status.
209  """
210  with misc.ChDir(config.chromium_path, verbose=config.verbose):
211    git_utils.Fetch()
212    output = shell_utils.run([git_utils.GIT, 'show', 'origin/master:DEPS'],
213                             log_in_real_time=False).rstrip()
214    match = re.search(REGEXP_SKIA_REVISION, output, flags=re.MULTILINE)
215    old_revision = None
216    if match:
217      old_revision = match.group('revision')
218    assert old_revision
219
220    master_hash = git_utils.FullHash('origin/master').rstrip()
221
222    # master_hash[8] gives each whitespace CL a unique name.
223    branch = 'control_%s' % master_hash[:8]
224    message = ('whitespace change %s\n\n'
225               'Chromium base revision: %s\n\n'
226               'This CL was created by Skia\'s roll_deps.py script.\n'
227               ) % (master_hash[:8], master_hash[:8])
228    with git_utils.GitBranch(branch, message,
229                             delete_when_finished=not config.save_branches,
230                             upload=not config.skip_cl_upload
231                             ) as whitespace_branch:
232      branch = git_utils.GetCurrentBranch()
233      with open(os.path.join('build', 'whitespace_file.txt'), 'a') as f:
234        f.write('\nCONTROL\n')
235
236      control_url = whitespace_branch.commit_and_upload()
237      if config.cl_bot_list:
238        submit_tries(config.cl_bot_list, dry_run=config.skip_cl_upload)
239      whitespace_cl = control_url
240      if config.save_branches:
241        whitespace_cl += '\n    branch: %s' % branch
242
243    branch = 'roll_%s_%s' % (revision, master_hash[:8])
244    message = (
245        'roll skia DEPS to %s\n\n'
246        'Chromium base revision: %s\n'
247        'Old Skia revision: %s\n'
248        'New Skia revision: %s\n'
249        'Control CL: %s\n\n'
250        'This CL was created by Skia\'s roll_deps.py script.\n\n'
251        'Bypassing commit queue trybots:\n'
252        'NOTRY=true\n'
253        % (revision, master_hash[:8],
254           old_revision[:8], revision[:8], control_url))
255    with git_utils.GitBranch(branch, message,
256                             delete_when_finished=not config.save_branches,
257                             upload=not config.skip_cl_upload
258                             ) as roll_branch:
259      change_skia_deps(revision, 'DEPS')
260      deps_url = roll_branch.commit_and_upload()
261      if config.cl_bot_list:
262        submit_tries(config.cl_bot_list, dry_run=config.skip_cl_upload)
263      deps_cl = deps_url
264      if config.save_branches:
265        deps_cl += '\n    branch: %s' % branch
266
267    return deps_cl, whitespace_cl
268
269
270def main(args):
271  """main function; see module-level docstring and GetOptionParser help.
272
273  Args:
274      args: sys.argv[1:]-type argument list.
275  """
276  option_parser = DepsRollConfig.GetOptionParser()
277  options = option_parser.parse_args(args)[0]
278
279  if not options.revision:
280    option_parser.error('Must specify a revision.')
281  if not options.chromium_path:
282    option_parser.error('Must specify chromium_path.')
283  if not os.path.isdir(options.chromium_path):
284    option_parser.error('chromium_path must be a directory.')
285
286  config = DepsRollConfig(options)
287  shell_utils.VERBOSE = options.verbose
288  deps_issue, whitespace_issue = roll_deps(config, options.revision)
289
290  if deps_issue and whitespace_issue:
291    print 'DEPS roll:\n    %s\n' % deps_issue
292    print 'Whitespace change:\n    %s\n' % whitespace_issue
293  else:
294    print >> sys.stderr, 'No issues created.'
295
296
297if __name__ == '__main__':
298  main(sys.argv[1:])
299