1#!/usr/bin/env python 2# Copyright 2019 The ANGLE project authors. All Rights Reserved. 3# 4# Use of this source code is governed by a BSD-style license 5# that can be found in the LICENSE file in the root of the source 6# tree. An additional intellectual property rights grant can be found 7# in the file PATENTS. All contributing project authors may 8# be found in the AUTHORS file in the root of the source tree. 9 10# This is a modified copy of the script in 11# https://webrtc.googlesource.com/src/+/master/tools_webrtc/autoroller/roll_deps.py 12# customized for ANGLE. 13"""Script to automatically roll Chromium dependencies in the ANGLE DEPS file.""" 14 15import argparse 16import base64 17import collections 18import logging 19import os 20import platform 21import re 22import subprocess 23import sys 24import urllib2 25 26 27def FindSrcDirPath(): 28 """Returns the abs path to the root dir of the project.""" 29 # Special cased for ANGLE. 30 return os.path.dirname(os.path.abspath(os.path.join(__file__, '..'))) 31 32 33ANGLE_CHROMIUM_DEPS = [ 34 'build', 35 'buildtools', 36 'testing', 37 'third_party/googletest', 38 'third_party/libjpeg_turbo', 39 'third_party/jsoncpp', 40 'third_party/Python-Markdown', 41 'third_party/qemu-linux-x64', 42 'third_party/qemu-mac-x64', 43 'third_party/yasm', 44 'third_party/zlib', 45 'tools/clang', 46 'tools/md_browser', 47 'tools/memory', 48] 49 50ANGLE_URL = 'https://chromium.googlesource.com/angle/angle' 51CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src' 52CHROMIUM_COMMIT_TEMPLATE = CHROMIUM_SRC_URL + '/+/%s' 53CHROMIUM_LOG_TEMPLATE = CHROMIUM_SRC_URL + '/+log/%s' 54CHROMIUM_FILE_TEMPLATE = CHROMIUM_SRC_URL + '/+/%s/%s' 55 56COMMIT_POSITION_RE = re.compile('^Cr-Commit-Position: .*#([0-9]+).*$') 57CLANG_REVISION_RE = re.compile(r'^CLANG_REVISION = \'([0-9a-z]+)\'') 58ROLL_BRANCH_NAME = 'roll_chromium_revision' 59 60SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 61CHECKOUT_SRC_DIR = FindSrcDirPath() 62CHECKOUT_ROOT_DIR = CHECKOUT_SRC_DIR 63 64# Copied from tools/android/roll/android_deps/.../BuildConfigGenerator.groovy. 65ANDROID_DEPS_START = r'=== ANDROID_DEPS Generated Code Start ===' 66ANDROID_DEPS_END = r'=== ANDROID_DEPS Generated Code End ===' 67# Location of automically gathered android deps. 68ANDROID_DEPS_PATH = 'src/third_party/android_deps/' 69 70# TODO(jmadill): Update this with ANGLE wrangler. http://anglebug.com/4059 71NOTIFY_EMAIL = 'jmadill@chromium.org' 72 73sys.path.append(os.path.join(CHECKOUT_SRC_DIR, 'build')) 74import find_depot_tools 75 76find_depot_tools.add_depot_tools_to_path() 77 78CLANG_UPDATE_SCRIPT_URL_PATH = 'tools/clang/scripts/update.py' 79CLANG_UPDATE_SCRIPT_LOCAL_PATH = os.path.join(CHECKOUT_SRC_DIR, 'tools', 'clang', 'scripts', 80 'update.py') 81 82DepsEntry = collections.namedtuple('DepsEntry', 'path url revision') 83ChangedDep = collections.namedtuple('ChangedDep', 'path url current_rev new_rev') 84CipdDepsEntry = collections.namedtuple('CipdDepsEntry', 'path packages') 85ChangedCipdPackage = collections.namedtuple('ChangedCipdPackage', 86 'path package current_version new_version') 87 88ChromiumRevisionUpdate = collections.namedtuple('ChromiumRevisionUpdate', ('current_chromium_rev ' 89 'new_chromium_rev ')) 90 91 92class RollError(Exception): 93 pass 94 95 96def VarLookup(local_scope): 97 return lambda var_name: local_scope['vars'][var_name] 98 99 100def ParseDepsDict(deps_content): 101 local_scope = {} 102 global_scope = { 103 'Var': VarLookup(local_scope), 104 'deps_os': {}, 105 } 106 exec (deps_content, global_scope, local_scope) 107 return local_scope 108 109 110def ParseLocalDepsFile(filename): 111 with open(filename, 'rb') as f: 112 deps_content = f.read() 113 return ParseDepsDict(deps_content) 114 115 116def ParseCommitPosition(commit_message): 117 for line in reversed(commit_message.splitlines()): 118 m = COMMIT_POSITION_RE.match(line.strip()) 119 if m: 120 return int(m.group(1)) 121 logging.error('Failed to parse commit position id from:\n%s\n', commit_message) 122 sys.exit(-1) 123 124 125def _RunCommand(command, working_dir=None, ignore_exit_code=False, extra_env=None, 126 input_data=None): 127 """Runs a command and returns the output from that command. 128 129 If the command fails (exit code != 0), the function will exit the process. 130 131 Returns: 132 A tuple containing the stdout and stderr outputs as strings. 133 """ 134 working_dir = working_dir or CHECKOUT_SRC_DIR 135 logging.debug('CMD: %s CWD: %s', ' '.join(command), working_dir) 136 env = os.environ.copy() 137 if extra_env: 138 assert all(isinstance(value, str) for value in extra_env.values()) 139 logging.debug('extra env: %s', extra_env) 140 env.update(extra_env) 141 p = subprocess.Popen( 142 command, 143 stdin=subprocess.PIPE, 144 stdout=subprocess.PIPE, 145 stderr=subprocess.PIPE, 146 env=env, 147 cwd=working_dir, 148 universal_newlines=True) 149 std_output, err_output = p.communicate(input_data) 150 p.stdout.close() 151 p.stderr.close() 152 if not ignore_exit_code and p.returncode != 0: 153 logging.error('Command failed: %s\n' 154 'stdout:\n%s\n' 155 'stderr:\n%s\n', ' '.join(command), std_output, err_output) 156 sys.exit(p.returncode) 157 return std_output, err_output 158 159 160def _GetBranches(): 161 """Returns a tuple of active,branches. 162 163 The 'active' is the name of the currently active branch and 'branches' is a 164 list of all branches. 165 """ 166 lines = _RunCommand(['git', 'branch'])[0].split('\n') 167 branches = [] 168 active = '' 169 for line in lines: 170 if '*' in line: 171 # The assumption is that the first char will always be the '*'. 172 active = line[1:].strip() 173 branches.append(active) 174 else: 175 branch = line.strip() 176 if branch: 177 branches.append(branch) 178 return active, branches 179 180 181def _ReadGitilesContent(url): 182 # Download and decode BASE64 content until 183 # https://code.google.com/p/gitiles/issues/detail?id=7 is fixed. 184 base64_content = ReadUrlContent(url + '?format=TEXT') 185 return base64.b64decode(base64_content[0]) 186 187 188def ReadRemoteCrFile(path_below_src, revision): 189 """Reads a remote Chromium file of a specific revision. Returns a string.""" 190 return _ReadGitilesContent(CHROMIUM_FILE_TEMPLATE % (revision, path_below_src)) 191 192 193def ReadRemoteCrCommit(revision): 194 """Reads a remote Chromium commit message. Returns a string.""" 195 return _ReadGitilesContent(CHROMIUM_COMMIT_TEMPLATE % revision) 196 197 198def ReadUrlContent(url): 199 """Connect to a remote host and read the contents. Returns a list of lines.""" 200 conn = urllib2.urlopen(url) 201 try: 202 return conn.readlines() 203 except IOError as e: 204 logging.exception('Error connecting to %s. Error: %s', url, e) 205 raise 206 finally: 207 conn.close() 208 209 210def GetMatchingDepsEntries(depsentry_dict, dir_path): 211 """Gets all deps entries matching the provided path. 212 213 This list may contain more than one DepsEntry object. 214 Example: dir_path='src/testing' would give results containing both 215 'src/testing/gtest' and 'src/testing/gmock' deps entries for Chromium's DEPS. 216 Example 2: dir_path='src/build' should return 'src/build' but not 217 'src/buildtools'. 218 219 Returns: 220 A list of DepsEntry objects. 221 """ 222 result = [] 223 for path, depsentry in depsentry_dict.iteritems(): 224 if path == dir_path: 225 result.append(depsentry) 226 else: 227 parts = path.split('/') 228 if all(part == parts[i] for i, part in enumerate(dir_path.split('/'))): 229 result.append(depsentry) 230 return result 231 232 233def BuildDepsentryDict(deps_dict): 234 """Builds a dict of paths to DepsEntry objects from a raw parsed deps dict.""" 235 result = {} 236 237 def AddDepsEntries(deps_subdict): 238 for path, dep in deps_subdict.iteritems(): 239 if path in result: 240 continue 241 if not isinstance(dep, dict): 242 dep = {'url': dep} 243 if dep.get('dep_type') == 'cipd': 244 result[path] = CipdDepsEntry(path, dep['packages']) 245 else: 246 if '@' not in dep['url']: 247 continue 248 url, revision = dep['url'].split('@') 249 result[path] = DepsEntry(path, url, revision) 250 251 AddDepsEntries(deps_dict['deps']) 252 for deps_os in ['win', 'mac', 'unix', 'android', 'ios', 'unix']: 253 AddDepsEntries(deps_dict.get('deps_os', {}).get(deps_os, {})) 254 return result 255 256 257def _FindChangedCipdPackages(path, old_pkgs, new_pkgs): 258 pkgs_equal = ({p['package'] for p in old_pkgs} == {p['package'] for p in new_pkgs}) 259 assert pkgs_equal, 'Old: %s\n New: %s' % (old_pkgs, new_pkgs) 260 for old_pkg in old_pkgs: 261 for new_pkg in new_pkgs: 262 old_version = old_pkg['version'] 263 new_version = new_pkg['version'] 264 if (old_pkg['package'] == new_pkg['package'] and old_version != new_version): 265 logging.debug('Roll dependency %s to %s', path, new_version) 266 yield ChangedCipdPackage(path, old_pkg['package'], old_version, new_version) 267 268 269def _FindNewDeps(old, new): 270 """ Gather dependencies only in |new| and return corresponding paths. """ 271 old_entries = set(BuildDepsentryDict(old)) 272 new_entries = set(BuildDepsentryDict(new)) 273 return [path for path in new_entries - old_entries if path in ANGLE_CHROMIUM_DEPS] 274 275 276def CalculateChangedDeps(angle_deps, new_cr_deps): 277 """ 278 Calculate changed deps entries based on entries defined in the ANGLE DEPS 279 file: 280 - If a shared dependency with the Chromium DEPS file: roll it to the same 281 revision as Chromium (i.e. entry in the new_cr_deps dict) 282 - If it's a Chromium sub-directory, roll it to the HEAD revision (notice 283 this means it may be ahead of the chromium_revision, but generally these 284 should be close). 285 - If it's another DEPS entry (not shared with Chromium), roll it to HEAD 286 unless it's configured to be skipped. 287 288 Returns: 289 A list of ChangedDep objects representing the changed deps. 290 """ 291 292 def ChromeURL(angle_deps_entry): 293 # Perform variable substitutions. 294 # This is a hack to get around the unsupported way this script parses DEPS. 295 # A better fix would be to use the gclient APIs to query and update DEPS. 296 # However this is complicated by how this script downloads DEPS remotely. 297 return angle_deps_entry.url.replace('{chromium_git}', 'https://chromium.googlesource.com') 298 299 result = [] 300 angle_entries = BuildDepsentryDict(angle_deps) 301 new_cr_entries = BuildDepsentryDict(new_cr_deps) 302 for path, angle_deps_entry in angle_entries.iteritems(): 303 if path not in ANGLE_CHROMIUM_DEPS: 304 continue 305 306 # All ANGLE Chromium dependencies are located in src/. 307 chrome_path = 'src/%s' % path 308 cr_deps_entry = new_cr_entries.get(chrome_path) 309 310 if cr_deps_entry: 311 assert type(cr_deps_entry) is type(angle_deps_entry) 312 313 if isinstance(cr_deps_entry, CipdDepsEntry): 314 result.extend( 315 _FindChangedCipdPackages(chrome_path, angle_deps_entry.packages, 316 cr_deps_entry.packages)) 317 continue 318 319 # Use the revision from Chromium's DEPS file. 320 new_rev = cr_deps_entry.revision 321 assert ChromeURL(angle_deps_entry) == cr_deps_entry.url, ( 322 'ANGLE DEPS entry %s has a different URL (%s) than Chromium (%s).' % 323 (path, ChromeURL(angle_deps_entry), cr_deps_entry.url)) 324 else: 325 if isinstance(angle_deps_entry, DepsEntry): 326 # Use the HEAD of the deps repo. 327 stdout, _ = _RunCommand(['git', 'ls-remote', ChromeURL(angle_deps_entry), 'HEAD']) 328 new_rev = stdout.strip().split('\t')[0] 329 else: 330 # The dependency has been removed from chromium. 331 # This is handled by FindRemovedDeps. 332 continue 333 334 # Check if an update is necessary. 335 if angle_deps_entry.revision != new_rev: 336 logging.debug('Roll dependency %s to %s', path, new_rev) 337 result.append( 338 ChangedDep(path, ChromeURL(angle_deps_entry), angle_deps_entry.revision, new_rev)) 339 return sorted(result) 340 341 342def CalculateChangedClang(new_cr_rev): 343 344 def GetClangRev(lines): 345 for line in lines: 346 match = CLANG_REVISION_RE.match(line) 347 if match: 348 return match.group(1) 349 raise RollError('Could not parse Clang revision!') 350 351 with open(CLANG_UPDATE_SCRIPT_LOCAL_PATH, 'rb') as f: 352 current_lines = f.readlines() 353 current_rev = GetClangRev(current_lines) 354 355 new_clang_update_py = ReadRemoteCrFile(CLANG_UPDATE_SCRIPT_URL_PATH, new_cr_rev).splitlines() 356 new_rev = GetClangRev(new_clang_update_py) 357 return ChangedDep(CLANG_UPDATE_SCRIPT_LOCAL_PATH, None, current_rev, new_rev) 358 359 360def GenerateCommitMessage( 361 rev_update, 362 current_commit_pos, 363 new_commit_pos, 364 changed_deps_list, 365 clang_change=None, 366): 367 current_cr_rev = rev_update.current_chromium_rev[0:10] 368 new_cr_rev = rev_update.new_chromium_rev[0:10] 369 rev_interval = '%s..%s' % (current_cr_rev, new_cr_rev) 370 git_number_interval = '%s:%s' % (current_commit_pos, new_commit_pos) 371 372 commit_msg = [ 373 'Roll chromium_revision %s (%s)\n' % (rev_interval, git_number_interval), 374 'Change log: %s' % (CHROMIUM_LOG_TEMPLATE % rev_interval), 375 'Full diff: %s\n' % (CHROMIUM_COMMIT_TEMPLATE % rev_interval) 376 ] 377 378 def Section(adjective, deps): 379 noun = 'dependency' if len(deps) == 1 else 'dependencies' 380 commit_msg.append('%s %s' % (adjective, noun)) 381 382 tbr_authors = '' 383 if changed_deps_list: 384 Section('Changed', changed_deps_list) 385 386 for c in changed_deps_list: 387 if isinstance(c, ChangedCipdPackage): 388 commit_msg.append('* %s: %s..%s' % (c.path, c.current_version, c.new_version)) 389 else: 390 commit_msg.append( 391 '* %s: %s/+log/%s..%s' % (c.path, c.url, c.current_rev[0:10], c.new_rev[0:10])) 392 393 if changed_deps_list: 394 change_url = CHROMIUM_FILE_TEMPLATE % (rev_interval, 'DEPS') 395 commit_msg.append('DEPS diff: %s\n' % change_url) 396 else: 397 commit_msg.append('No dependencies changed.') 398 399 if clang_change and clang_change.current_rev != clang_change.new_rev: 400 commit_msg.append( 401 'Clang version changed %s:%s' % (clang_change.current_rev, clang_change.new_rev)) 402 change_url = CHROMIUM_FILE_TEMPLATE % (rev_interval, CLANG_UPDATE_SCRIPT_URL_PATH) 403 commit_msg.append('Details: %s\n' % change_url) 404 else: 405 commit_msg.append('No update to Clang.\n') 406 407 # TBR needs to be non-empty for Gerrit to process it. 408 git_author = _RunCommand(['git', 'config', 'user.email'], 409 working_dir=CHECKOUT_SRC_DIR)[0].splitlines()[0] 410 tbr_authors = git_author + ',' + tbr_authors 411 412 commit_msg.append('TBR=%s' % tbr_authors) 413 commit_msg.append('BUG=None') 414 return '\n'.join(commit_msg) 415 416 417def UpdateDepsFile(deps_filename, rev_update, changed_deps, new_cr_content): 418 """Update the DEPS file with the new revision.""" 419 420 with open(deps_filename, 'rb') as deps_file: 421 deps_content = deps_file.read() 422 423 # Update the chromium_revision variable. 424 deps_content = deps_content.replace(rev_update.current_chromium_rev, 425 rev_update.new_chromium_rev) 426 427 with open(deps_filename, 'wb') as deps_file: 428 deps_file.write(deps_content) 429 430 # Update each individual DEPS entry. 431 for dep in changed_deps: 432 local_dep_dir = os.path.join(CHECKOUT_ROOT_DIR, dep.path) 433 if not os.path.isdir(local_dep_dir): 434 raise RollError('Cannot find local directory %s. Either run\n' 435 'gclient sync --deps=all\n' 436 'or make sure the .gclient file for your solution contains all ' 437 'platforms in the target_os list, i.e.\n' 438 'target_os = ["android", "unix", "mac", "ios", "win"];\n' 439 'Then run "gclient sync" again.' % local_dep_dir) 440 if isinstance(dep, ChangedCipdPackage): 441 package = dep.package.format() # Eliminate double curly brackets 442 update = '%s:%s@%s' % (dep.path, package, dep.new_version) 443 else: 444 update = '%s@%s' % (dep.path, dep.new_rev) 445 gclient_cmd = 'gclient' 446 if platform.system() == 'Windows': 447 gclient_cmd += '.bat' 448 _RunCommand([gclient_cmd, 'setdep', '--revision', update], working_dir=CHECKOUT_SRC_DIR) 449 450 451def _IsTreeClean(): 452 stdout, _ = _RunCommand(['git', 'status', '--porcelain']) 453 if len(stdout) == 0: 454 return True 455 456 logging.error('Dirty/unversioned files:\n%s', stdout) 457 return False 458 459 460def _EnsureUpdatedMasterBranch(dry_run): 461 current_branch = _RunCommand(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])[0].splitlines()[0] 462 if current_branch != 'master': 463 logging.error('Please checkout the master branch and re-run this script.') 464 if not dry_run: 465 sys.exit(-1) 466 467 logging.info('Updating master branch...') 468 _RunCommand(['git', 'pull']) 469 470 471def _CreateRollBranch(dry_run): 472 logging.info('Creating roll branch: %s', ROLL_BRANCH_NAME) 473 if not dry_run: 474 _RunCommand(['git', 'checkout', '-b', ROLL_BRANCH_NAME]) 475 476 477def _RemovePreviousRollBranch(dry_run): 478 active_branch, branches = _GetBranches() 479 if active_branch == ROLL_BRANCH_NAME: 480 active_branch = 'master' 481 if ROLL_BRANCH_NAME in branches: 482 logging.info('Removing previous roll branch (%s)', ROLL_BRANCH_NAME) 483 if not dry_run: 484 _RunCommand(['git', 'checkout', active_branch]) 485 _RunCommand(['git', 'branch', '-D', ROLL_BRANCH_NAME]) 486 487 488def _LocalCommit(commit_msg, dry_run): 489 logging.info('Committing changes locally.') 490 if not dry_run: 491 _RunCommand(['git', 'add', '--update', '.']) 492 _RunCommand(['git', 'commit', '-m', commit_msg]) 493 494 495def ChooseCQMode(skip_cq, cq_over, current_commit_pos, new_commit_pos): 496 if skip_cq: 497 return 0 498 if (new_commit_pos - current_commit_pos) < cq_over: 499 return 1 500 return 2 501 502 503def _UploadCL(commit_queue_mode): 504 """Upload the committed changes as a changelist to Gerrit. 505 506 commit_queue_mode: 507 - 2: Submit to commit queue. 508 - 1: Run trybots but do not submit to CQ. 509 - 0: Skip CQ, upload only. 510 """ 511 cmd = ['git', 'cl', 'upload', '--force', '--bypass-hooks', '--send-mail'] 512 cmd.extend(['--cc', NOTIFY_EMAIL]) 513 if commit_queue_mode >= 2: 514 logging.info('Sending the CL to the CQ...') 515 cmd.extend(['--use-commit-queue']) 516 elif commit_queue_mode >= 1: 517 logging.info('Starting CQ dry run...') 518 cmd.extend(['--cq-dry-run']) 519 extra_env = { 520 'EDITOR': 'true', 521 'SKIP_GCE_AUTH_FOR_GIT': '1', 522 } 523 stdout, stderr = _RunCommand(cmd, extra_env=extra_env) 524 logging.debug('Output from "git cl upload":\nstdout:\n%s\n\nstderr:\n%s', stdout, stderr) 525 526 527def GetRollRevisionRanges(opts, angle_deps): 528 current_cr_rev = angle_deps['vars']['chromium_revision'] 529 new_cr_rev = opts.revision 530 if not new_cr_rev: 531 stdout, _ = _RunCommand(['git', 'ls-remote', CHROMIUM_SRC_URL, 'HEAD']) 532 head_rev = stdout.strip().split('\t')[0] 533 logging.info('No revision specified. Using HEAD: %s', head_rev) 534 new_cr_rev = head_rev 535 536 return ChromiumRevisionUpdate(current_cr_rev, new_cr_rev) 537 538 539def main(): 540 p = argparse.ArgumentParser() 541 p.add_argument( 542 '--clean', 543 action='store_true', 544 default=False, 545 help='Removes any previous local roll branch.') 546 p.add_argument( 547 '-r', 548 '--revision', 549 help=('Chromium Git revision to roll to. Defaults to the ' 550 'Chromium HEAD revision if omitted.')) 551 p.add_argument( 552 '--dry-run', 553 action='store_true', 554 default=False, 555 help=('Calculate changes and modify DEPS, but don\'t create ' 556 'any local branch, commit, upload CL or send any ' 557 'tryjobs.')) 558 p.add_argument( 559 '-i', 560 '--ignore-unclean-workdir', 561 action='store_true', 562 default=False, 563 help=('Ignore if the current branch is not master or if there ' 564 'are uncommitted changes (default: %(default)s).')) 565 grp = p.add_mutually_exclusive_group() 566 grp.add_argument( 567 '--skip-cq', 568 action='store_true', 569 default=False, 570 help='Skip sending the CL to the CQ (default: %(default)s)') 571 grp.add_argument( 572 '--cq-over', 573 type=int, 574 default=1, 575 help=('Commit queue dry run if the revision difference ' 576 'is below this number (default: %(default)s)')) 577 p.add_argument( 578 '-v', 579 '--verbose', 580 action='store_true', 581 default=False, 582 help='Be extra verbose in printing of log messages.') 583 opts = p.parse_args() 584 585 if opts.verbose: 586 logging.basicConfig(level=logging.DEBUG) 587 else: 588 logging.basicConfig(level=logging.INFO) 589 590 if not opts.ignore_unclean_workdir and not _IsTreeClean(): 591 logging.error('Please clean your local checkout first.') 592 return 1 593 594 if opts.clean: 595 _RemovePreviousRollBranch(opts.dry_run) 596 597 if not opts.ignore_unclean_workdir: 598 _EnsureUpdatedMasterBranch(opts.dry_run) 599 600 deps_filename = os.path.join(CHECKOUT_SRC_DIR, 'DEPS') 601 angle_deps = ParseLocalDepsFile(deps_filename) 602 603 rev_update = GetRollRevisionRanges(opts, angle_deps) 604 605 current_commit_pos = ParseCommitPosition(ReadRemoteCrCommit(rev_update.current_chromium_rev)) 606 new_commit_pos = ParseCommitPosition(ReadRemoteCrCommit(rev_update.new_chromium_rev)) 607 608 new_cr_content = ReadRemoteCrFile('DEPS', rev_update.new_chromium_rev) 609 new_cr_deps = ParseDepsDict(new_cr_content) 610 changed_deps = CalculateChangedDeps(angle_deps, new_cr_deps) 611 clang_change = CalculateChangedClang(rev_update.new_chromium_rev) 612 commit_msg = GenerateCommitMessage( 613 rev_update, current_commit_pos, new_commit_pos, changed_deps, clang_change=clang_change) 614 logging.debug('Commit message:\n%s', commit_msg) 615 616 _CreateRollBranch(opts.dry_run) 617 if not opts.dry_run: 618 UpdateDepsFile(deps_filename, rev_update, changed_deps, new_cr_content) 619 if _IsTreeClean(): 620 logging.info("No DEPS changes detected, skipping CL creation.") 621 else: 622 _LocalCommit(commit_msg, opts.dry_run) 623 commit_queue_mode = ChooseCQMode(opts.skip_cq, opts.cq_over, current_commit_pos, 624 new_commit_pos) 625 logging.info('Uploading CL...') 626 if not opts.dry_run: 627 _UploadCL(commit_queue_mode) 628 return 0 629 630 631if __name__ == '__main__': 632 sys.exit(main()) 633