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