Luis Hector Chavez | b50391d | 2017-09-26 15:48:15 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python2 |
| 2 | # -*- coding:utf-8 -*- |
| 3 | # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | |
| 7 | """Wrapper to run git-clang-format and parse its output.""" |
| 8 | |
| 9 | from __future__ import print_function |
| 10 | |
| 11 | import hashlib |
| 12 | import io |
| 13 | import os |
| 14 | import sys |
| 15 | |
| 16 | _path = os.path.realpath(__file__ + '/../../..') |
| 17 | if sys.path[0] != _path: |
| 18 | sys.path.insert(0, _path) |
| 19 | del _path |
| 20 | |
| 21 | from chromite.lib import commandline |
| 22 | from chromite.lib import constants |
| 23 | from chromite.lib import cros_build_lib |
| 24 | |
| 25 | |
| 26 | # Since we're asking git-clang-format to print a diff, all modified filenames |
| 27 | # that have formatting errors are printed with this prefix. |
| 28 | DIFF_MARKER_PREFIX = '+++ b/' |
| 29 | |
Mike Frysinger | df02d59 | 2019-02-19 18:21:43 -0500 | [diff] [blame^] | 30 | BUILDTOOLS_PATH = os.path.join(constants.SOURCE_ROOT, 'src', 'chromium', 'src', |
Luis Hector Chavez | b50391d | 2017-09-26 15:48:15 -0700 | [diff] [blame] | 31 | 'buildtools') |
| 32 | |
| 33 | |
| 34 | def _GetSha1Hash(path): |
| 35 | """Gets the SHA-1 hash of |path|, or None if the file does not exist.""" |
| 36 | if not os.path.exists(path): |
| 37 | return None |
| 38 | with open(path, 'rb') as f: |
| 39 | m = hashlib.sha1() |
| 40 | while True: |
| 41 | buf = f.read(io.DEFAULT_BUFFER_SIZE) |
| 42 | if not buf: |
| 43 | break |
| 44 | m.update(buf) |
| 45 | return m.hexdigest() |
| 46 | |
| 47 | |
| 48 | def _GetDefaultClangFormatPath(): |
| 49 | """Gets the default clang-format binary path. |
| 50 | |
| 51 | This also ensures that the binary itself is up-to-date. |
| 52 | """ |
| 53 | |
| 54 | clang_format_path = os.path.join(BUILDTOOLS_PATH, 'linux64/clang-format') |
| 55 | hash_file_path = os.path.join(BUILDTOOLS_PATH, 'linux64/clang-format.sha1') |
| 56 | with open(hash_file_path, 'r') as f: |
| 57 | expected_hash = f.read().strip() |
| 58 | if expected_hash != _GetSha1Hash(clang_format_path): |
| 59 | # See chromium/src/buildtools/clang_format/README.txt for more details. |
| 60 | cmd = [os.path.join(constants.DEPOT_TOOLS_DIR, |
| 61 | 'download_from_google_storage.py'), '-b', |
| 62 | 'chromium-clang-format', '-s', hash_file_path] |
| 63 | cros_build_lib.RunCommand(cmd=cmd, print_cmd=False) |
| 64 | return clang_format_path |
| 65 | |
| 66 | |
| 67 | def main(argv): |
| 68 | """Checks if a project is correctly formatted with clang-format. |
| 69 | |
| 70 | Returns 1 if there are any clang-format-worthy changes in the project (or |
| 71 | on a provided list of files/directories in the project), 0 otherwise. |
| 72 | """ |
| 73 | |
| 74 | parser = commandline.ArgumentParser(description=__doc__) |
| 75 | parser.add_argument('--clang-format', default=_GetDefaultClangFormatPath(), |
| 76 | help='The path of the clang-format executable.') |
| 77 | parser.add_argument('--git-clang-format', |
| 78 | default=os.path.join(BUILDTOOLS_PATH, 'clang_format', |
| 79 | 'script', 'git-clang-format'), |
| 80 | help='The path of the git-clang-format executable.') |
| 81 | parser.add_argument('--style', metavar='STYLE', type=str, default='file', |
| 82 | help='The style that clang-format will use.') |
| 83 | parser.add_argument('--extensions', metavar='EXTENSIONS', type=str, |
| 84 | help='Comma-separated list of file extensions to ' |
| 85 | 'format.') |
| 86 | parser.add_argument('--fix', action='store_true', |
| 87 | help='Fix any formatting errors automatically.') |
| 88 | |
| 89 | scope = parser.add_mutually_exclusive_group(required=True) |
| 90 | scope.add_argument('--commit', type=str, default='HEAD', |
| 91 | help='Specify the commit to validate.') |
| 92 | scope.add_argument('--working-tree', action='store_true', |
| 93 | help='Validates the files that have changed from ' |
| 94 | 'HEAD in the working directory.') |
| 95 | |
| 96 | parser.add_argument('files', type=str, nargs='*', |
| 97 | help='If specified, only consider differences in ' |
| 98 | 'these files/directories.') |
| 99 | |
| 100 | opts = parser.parse_args(argv) |
| 101 | |
| 102 | cmd = [opts.git_clang_format, '--binary', opts.clang_format, '--diff'] |
| 103 | if opts.style: |
| 104 | cmd.extend(['--style', opts.style]) |
| 105 | if opts.extensions: |
| 106 | cmd.extend(['--extensions', opts.extensions]) |
| 107 | if not opts.working_tree: |
| 108 | cmd.extend(['%s^' % opts.commit, opts.commit]) |
| 109 | cmd.extend(['--'] + opts.files) |
| 110 | |
| 111 | # Fail gracefully if clang-format itself aborts/fails. |
| 112 | try: |
| 113 | result = cros_build_lib.RunCommand(cmd=cmd, |
| 114 | print_cmd=False, |
| 115 | stdout_to_pipe=True) |
| 116 | except cros_build_lib.RunCommandError as e: |
| 117 | print('clang-format failed:\n' + str(e), file=sys.stderr) |
| 118 | print('\nPlease report this to the clang team.', file=sys.stderr) |
| 119 | return 1 |
| 120 | |
| 121 | stdout = result.output |
| 122 | if stdout.rstrip('\n') == 'no modified files to format': |
| 123 | # This is always printed when only files that clang-format does not |
| 124 | # understand were modified. |
| 125 | return 0 |
| 126 | |
| 127 | diff_filenames = [] |
| 128 | for line in stdout.splitlines(): |
| 129 | if line.startswith(DIFF_MARKER_PREFIX): |
| 130 | diff_filenames.append(line[len(DIFF_MARKER_PREFIX):].rstrip()) |
| 131 | |
| 132 | if diff_filenames: |
| 133 | if opts.fix: |
| 134 | cros_build_lib.RunCommand(cmd=['git', 'apply'], |
| 135 | print_cmd=False, |
| 136 | input=stdout) |
| 137 | else: |
| 138 | print('The following files have formatting errors:') |
| 139 | for filename in diff_filenames: |
| 140 | print('\t%s' % filename) |
| 141 | print('You can run `%s --fix %s` to fix this' % |
| 142 | (sys.argv[0], |
| 143 | ' '.join(cros_build_lib.ShellQuote(arg) for arg in argv))) |
| 144 | return 1 |
| 145 | |
| 146 | if __name__ == '__main__': |
| 147 | commandline.ScriptWrapperMain(lambda _: main) |