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