blob: 84f9429afa03eed357f531692b2945039ba069cc [file] [log] [blame]
Luis Hector Chavezb50391d2017-09-26 15:48:15 -07001#!/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
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
21from chromite.lib import commandline
22from chromite.lib import constants
23from 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.
28DIFF_MARKER_PREFIX = '+++ b/'
29
Mike Frysingerdf02d592019-02-19 18:21:43 -050030BUILDTOOLS_PATH = os.path.join(constants.SOURCE_ROOT, 'src', 'chromium', 'src',
Luis Hector Chavezb50391d2017-09-26 15:48:15 -070031 'buildtools')
32
33
34def _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
48def _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
67def 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
146if __name__ == '__main__':
147 commandline.ScriptWrapperMain(lambda _: main)