• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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