blob: cea0cafe7381f8a0dd1aa54babfe79fd1e1e202c [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
Luis Hector Chavezb50391d2017-09-26 15:48:15 -07003# 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
9from __future__ import print_function
10
11import hashlib
12import io
13import os
14import sys
15
16_path = os.path.realpath(__file__ + '/../../..')
17if sys.path[0] != _path:
18 sys.path.insert(0, _path)
19del _path
20
Mike Frysingerfd481ce2019-09-13 18:14:48 -040021# The sys.path monkey patching confuses the linter.
22# pylint: disable=wrong-import-position
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070023from chromite.lib import commandline
24from chromite.lib import constants
25from chromite.lib import cros_build_lib
26
27
Mike Frysingerff4768e2020-02-27 18:48:13 -050028assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
29
30
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070031# Since we're asking git-clang-format to print a diff, all modified filenames
32# that have formatting errors are printed with this prefix.
33DIFF_MARKER_PREFIX = '+++ b/'
34
Mike Frysingerdf02d592019-02-19 18:21:43 -050035BUILDTOOLS_PATH = os.path.join(constants.SOURCE_ROOT, 'src', 'chromium', 'src',
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070036 'buildtools')
37
38
39def _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
53def _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 Frysinger7bb709f2019-09-29 23:20:12 -040068 cros_build_lib.run(cmd, print_cmd=False)
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070069 return clang_format_path
70
71
72def 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 Frysinger7bb709f2019-09-29 23:20:12 -0400118 result = cros_build_lib.run(cmd, print_cmd=False, stdout=True,
119 encoding='utf-8', errors='replace')
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700120 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 Frysinger7bb709f2019-09-29 23:20:12 -0400125 stdout = result.stdout
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700126 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 Frysinger7bb709f2019-09-29 23:20:12 -0400138 cros_build_lib.run(['git', 'apply'], print_cmd=False, input=stdout)
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700139 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 Frysinger8cf80812019-09-16 23:49:29 -0400148 return 0
149
150
Luis Hector Chavezb50391d2017-09-26 15:48:15 -0700151if __name__ == '__main__':
152 commandline.ScriptWrapperMain(lambda _: main)