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