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