blob: 124f107a355df64e41e183ad477d9a51b2da3153 [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
Luis Hector Chavezb50391d2017-09-26 15:48:15 -07002# 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 Chavezb50391d2017-09-26 15:48:15 -07008import hashlib
9import io
10import os
11import sys
12
13_path = os.path.realpath(__file__ + '/../../..')
14if sys.path[0] != _path:
15 sys.path.insert(0, _path)
16del _path
17
Mike Frysingerfd481ce2019-09-13 18:14:48 -040018# The sys.path monkey patching confuses the linter.
19# pylint: disable=wrong-import-position
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070020from chromite.lib import commandline
21from chromite.lib import constants
22from chromite.lib import cros_build_lib
23
24
Mike Frysingerff4768e2020-02-27 18:48:13 -050025assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
26
27
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070028# Since we're asking git-clang-format to print a diff, all modified filenames
29# that have formatting errors are printed with this prefix.
30DIFF_MARKER_PREFIX = '+++ b/'
31
Mike Frysingerdf02d592019-02-19 18:21:43 -050032BUILDTOOLS_PATH = os.path.join(constants.SOURCE_ROOT, 'src', 'chromium', 'src',
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070033 'buildtools')
34
35
36def _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
50def _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 Frysinger7bb709f2019-09-29 23:20:12 -040065 cros_build_lib.run(cmd, print_cmd=False)
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070066 return clang_format_path
67
68
69def 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 Frysinger7bb709f2019-09-29 23:20:12 -0400115 result = cros_build_lib.run(cmd, print_cmd=False, stdout=True,
116 encoding='utf-8', errors='replace')
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700117 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 Frysinger7bb709f2019-09-29 23:20:12 -0400122 stdout = result.stdout
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700123 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 Frysinger7bb709f2019-09-29 23:20:12 -0400135 cros_build_lib.run(['git', 'apply'], print_cmd=False, input=stdout)
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700136 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 Frysinger8cf80812019-09-16 23:49:29 -0400145 return 0
146
147
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700148if __name__ == '__main__':
149 commandline.ScriptWrapperMain(lambda _: main)