blob: 5df06b1095a0a9e73298e1c9dfa9369532240f72 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass26132882012-01-14 15:12:45 +00004
Simon Glass0495abf2013-03-26 13:09:39 +00005import collections
Simon Glassdc19cee2023-03-08 10:52:55 -08006import concurrent.futures
Simon Glass26132882012-01-14 15:12:45 +00007import os
8import re
Vadim Bendeburyc549f082013-01-09 16:00:10 +00009import sys
Simon Glassa997ea52020-04-17 18:09:04 -060010
Simon Glass131444f2023-02-23 18:18:04 -070011from u_boot_pylib import command
Simon Glassba1b3b92025-02-09 14:26:00 -070012from u_boot_pylib import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070013from u_boot_pylib import terminal
Evan Bennec7378b2021-04-01 13:49:30 +110014
15EMACS_PREFIX = r'(?:[0-9]{4}.*\.patch:[0-9]+: )?'
16TYPE_NAME = r'([A-Z_]+:)?'
17RE_ERROR = re.compile(r'ERROR:%s (.*)' % TYPE_NAME)
18RE_WARNING = re.compile(EMACS_PREFIX + r'WARNING:%s (.*)' % TYPE_NAME)
19RE_CHECK = re.compile(r'CHECK:%s (.*)' % TYPE_NAME)
20RE_FILE = re.compile(r'#(\d+): (FILE: ([^:]*):(\d+):)?')
21RE_NOTE = re.compile(r'NOTE: (.*)')
22
Simon Glass26132882012-01-14 15:12:45 +000023
Simon Glassd84e84a2022-01-29 14:14:06 -070024def find_check_patch():
Simon Glass761648b2022-01-29 14:14:11 -070025 top_level = gitutil.get_top_level()
Simon Glass26132882012-01-14 15:12:45 +000026 try_list = [
27 os.getcwd(),
28 os.path.join(os.getcwd(), '..', '..'),
Doug Anderson4baede02012-11-26 15:23:23 +000029 os.path.join(top_level, 'tools'),
30 os.path.join(top_level, 'scripts'),
Simon Glass26132882012-01-14 15:12:45 +000031 '%s/bin' % os.getenv('HOME'),
32 ]
33 # Look in current dir
34 for path in try_list:
35 fname = os.path.join(path, 'checkpatch.pl')
36 if os.path.isfile(fname):
37 return fname
38
39 # Look upwwards for a Chrome OS tree
40 while not os.path.ismount(path):
41 fname = os.path.join(path, 'src', 'third_party', 'kernel', 'files',
42 'scripts', 'checkpatch.pl')
43 if os.path.isfile(fname):
44 return fname
45 path = os.path.dirname(path)
Vadim Bendeburyc549f082013-01-09 16:00:10 +000046
Masahiro Yamada880828d2014-08-16 00:59:26 +090047 sys.exit('Cannot find checkpatch.pl - please put it in your ' +
48 '~/bin directory or use --no-check')
Simon Glass26132882012-01-14 15:12:45 +000049
Evan Bennec7378b2021-04-01 13:49:30 +110050
Simon Glassd84e84a2022-01-29 14:14:06 -070051def check_patch_parse_one_message(message):
Evan Bennec7378b2021-04-01 13:49:30 +110052 """Parse one checkpatch message
Simon Glass26132882012-01-14 15:12:45 +000053
Simon Glass24725af2020-07-05 21:41:49 -060054 Args:
Evan Bennec7378b2021-04-01 13:49:30 +110055 message: string to parse
56
57 Returns:
58 dict:
59 'type'; error or warning
60 'msg': text message
61 'file' : filename
62 'line': line number
63 """
64
65 if RE_NOTE.match(message):
66 return {}
67
68 item = {}
69
70 err_match = RE_ERROR.match(message)
71 warn_match = RE_WARNING.match(message)
72 check_match = RE_CHECK.match(message)
73 if err_match:
74 item['cptype'] = err_match.group(1)
75 item['msg'] = err_match.group(2)
76 item['type'] = 'error'
77 elif warn_match:
78 item['cptype'] = warn_match.group(1)
79 item['msg'] = warn_match.group(2)
80 item['type'] = 'warning'
81 elif check_match:
82 item['cptype'] = check_match.group(1)
83 item['msg'] = check_match.group(2)
84 item['type'] = 'check'
85 else:
86 message_indent = ' '
87 print('patman: failed to parse checkpatch message:\n%s' %
88 (message_indent + message.replace('\n', '\n' + message_indent)),
89 file=sys.stderr)
90 return {}
91
92 file_match = RE_FILE.search(message)
93 # some messages have no file, catch those here
94 no_file_match = any(s in message for s in [
95 '\nSubject:', 'Missing Signed-off-by: line(s)',
96 'does MAINTAINERS need updating'
97 ])
98
99 if file_match:
100 err_fname = file_match.group(3)
101 if err_fname:
102 item['file'] = err_fname
103 item['line'] = int(file_match.group(4))
104 else:
105 item['file'] = '<patch>'
106 item['line'] = int(file_match.group(1))
107 elif no_file_match:
108 item['file'] = '<patch>'
109 else:
110 message_indent = ' '
111 print('patman: failed to find file / line information:\n%s' %
112 (message_indent + message.replace('\n', '\n' + message_indent)),
113 file=sys.stderr)
114
115 return item
116
117
Simon Glassd84e84a2022-01-29 14:14:06 -0700118def check_patch_parse(checkpatch_output, verbose=False):
Evan Bennec7378b2021-04-01 13:49:30 +1100119 """Parse checkpatch.pl output
120
121 Args:
122 checkpatch_output: string to parse
Simon Glass24725af2020-07-05 21:41:49 -0600123 verbose: True to print out every line of the checkpatch output as it is
124 parsed
Simon Glass24725af2020-07-05 21:41:49 -0600125
Simon Glass26132882012-01-14 15:12:45 +0000126 Returns:
Simon Glass0495abf2013-03-26 13:09:39 +0000127 namedtuple containing:
128 ok: False=failure, True=ok
Simon Glass547cba62022-02-11 13:23:18 -0700129 problems (list of problems): each a dict:
Simon Glass26132882012-01-14 15:12:45 +0000130 'type'; error or warning
131 'msg': text message
132 'file' : filename
133 'line': line number
Simon Glass0495abf2013-03-26 13:09:39 +0000134 errors: Number of errors
135 warnings: Number of warnings
136 checks: Number of checks
Simon Glass26132882012-01-14 15:12:45 +0000137 lines: Number of lines
Evan Bennec7378b2021-04-01 13:49:30 +1100138 stdout: checkpatch_output
Simon Glass26132882012-01-14 15:12:45 +0000139 """
Simon Glass0495abf2013-03-26 13:09:39 +0000140 fields = ['ok', 'problems', 'errors', 'warnings', 'checks', 'lines',
141 'stdout']
142 result = collections.namedtuple('CheckPatchResult', fields)
Evan Bennec7378b2021-04-01 13:49:30 +1100143 result.stdout = checkpatch_output
Simon Glass0495abf2013-03-26 13:09:39 +0000144 result.ok = False
Simon Glass39d6d842020-05-06 16:29:04 -0600145 result.errors, result.warnings, result.checks = 0, 0, 0
Simon Glass0495abf2013-03-26 13:09:39 +0000146 result.lines = 0
147 result.problems = []
Simon Glass26132882012-01-14 15:12:45 +0000148
149 # total: 0 errors, 0 warnings, 159 lines checked
Simon Glass0495abf2013-03-26 13:09:39 +0000150 # or:
151 # total: 0 errors, 2 warnings, 7 checks, 473 lines checked
Evan Bennec7378b2021-04-01 13:49:30 +1100152 emacs_stats = r'(?:[0-9]{4}.*\.patch )?'
Simon Glass396168c2020-05-06 16:29:05 -0600153 re_stats = re.compile(emacs_stats +
Evan Bennec7378b2021-04-01 13:49:30 +1100154 r'total: (\d+) errors, (\d+) warnings, (\d+)')
Simon Glass396168c2020-05-06 16:29:05 -0600155 re_stats_full = re.compile(emacs_stats +
Evan Bennec7378b2021-04-01 13:49:30 +1100156 r'total: (\d+) errors, (\d+) warnings, (\d+)'
157 r' checks, (\d+)')
158 re_ok = re.compile(r'.*has no obvious style problems')
159 re_bad = re.compile(r'.*has style problems, please review')
160
161 # A blank line indicates the end of a message
162 for message in result.stdout.split('\n\n'):
Simon Glass26132882012-01-14 15:12:45 +0000163 if verbose:
Evan Bennec7378b2021-04-01 13:49:30 +1100164 print(message)
Simon Glass26132882012-01-14 15:12:45 +0000165
Evan Bennec7378b2021-04-01 13:49:30 +1100166 # either find stats, the verdict, or delegate
167 match = re_stats_full.match(message)
Simon Glass0495abf2013-03-26 13:09:39 +0000168 if not match:
Evan Bennec7378b2021-04-01 13:49:30 +1100169 match = re_stats.match(message)
Simon Glass26132882012-01-14 15:12:45 +0000170 if match:
Simon Glass0495abf2013-03-26 13:09:39 +0000171 result.errors = int(match.group(1))
172 result.warnings = int(match.group(2))
173 if len(match.groups()) == 4:
174 result.checks = int(match.group(3))
175 result.lines = int(match.group(4))
176 else:
177 result.lines = int(match.group(3))
Evan Bennec7378b2021-04-01 13:49:30 +1100178 elif re_ok.match(message):
Simon Glass0495abf2013-03-26 13:09:39 +0000179 result.ok = True
Evan Bennec7378b2021-04-01 13:49:30 +1100180 elif re_bad.match(message):
Simon Glass0495abf2013-03-26 13:09:39 +0000181 result.ok = False
Simon Glass7a543652020-05-06 16:29:09 -0600182 else:
Simon Glassd84e84a2022-01-29 14:14:06 -0700183 problem = check_patch_parse_one_message(message)
Evan Bennec7378b2021-04-01 13:49:30 +1100184 if problem:
185 result.problems.append(problem)
Simon Glass26132882012-01-14 15:12:45 +0000186
Simon Glass0495abf2013-03-26 13:09:39 +0000187 return result
Simon Glass26132882012-01-14 15:12:45 +0000188
Evan Bennec7378b2021-04-01 13:49:30 +1100189
Simon Glass5750cc22025-05-07 18:02:47 +0200190def check_patch(fname, verbose=False, show_types=False, use_tree=False,
191 cwd=None):
Evan Bennec7378b2021-04-01 13:49:30 +1100192 """Run checkpatch.pl on a file and parse the results.
193
194 Args:
195 fname: Filename to check
196 verbose: True to print out every line of the checkpatch output as it is
197 parsed
198 show_types: Tell checkpatch to show the type (number) of each message
Douglas Andersonbe4f2712022-07-19 14:56:27 -0700199 use_tree (bool): If False we'll pass '--no-tree' to checkpatch.
Simon Glass5750cc22025-05-07 18:02:47 +0200200 cwd (str): Path to use for patch files (None to use current dir)
Evan Bennec7378b2021-04-01 13:49:30 +1100201
202 Returns:
203 namedtuple containing:
204 ok: False=failure, True=ok
205 problems: List of problems, each a dict:
206 'type'; error or warning
207 'msg': text message
208 'file' : filename
209 'line': line number
210 errors: Number of errors
211 warnings: Number of warnings
212 checks: Number of checks
213 lines: Number of lines
214 stdout: Full output of checkpatch
215 """
Simon Glassd84e84a2022-01-29 14:14:06 -0700216 chk = find_check_patch()
Maxim Cournoyer6a407e12023-01-13 08:50:49 -0500217 args = [chk]
Douglas Andersonbe4f2712022-07-19 14:56:27 -0700218 if not use_tree:
219 args.append('--no-tree')
Evan Bennec7378b2021-04-01 13:49:30 +1100220 if show_types:
221 args.append('--show-types')
Simon Glass5750cc22025-05-07 18:02:47 +0200222 output = command.output(*args, os.path.join(cwd or '', fname),
223 raise_on_error=False)
Evan Bennec7378b2021-04-01 13:49:30 +1100224
Simon Glassd84e84a2022-01-29 14:14:06 -0700225 return check_patch_parse(output, verbose)
Evan Bennec7378b2021-04-01 13:49:30 +1100226
227
Simon Glassd84e84a2022-01-29 14:14:06 -0700228def get_warning_msg(col, msg_type, fname, line, msg):
Simon Glass26132882012-01-14 15:12:45 +0000229 '''Create a message for a given file/line
230
231 Args:
232 msg_type: Message type ('error' or 'warning')
233 fname: Filename which reports the problem
234 line: Line number where it was noticed
235 msg: Message to report
236 '''
237 if msg_type == 'warning':
Simon Glassf45d3742022-01-29 14:14:17 -0700238 msg_type = col.build(col.YELLOW, msg_type)
Simon Glass26132882012-01-14 15:12:45 +0000239 elif msg_type == 'error':
Simon Glassf45d3742022-01-29 14:14:17 -0700240 msg_type = col.build(col.RED, msg_type)
Simon Glass0495abf2013-03-26 13:09:39 +0000241 elif msg_type == 'check':
Simon Glassf45d3742022-01-29 14:14:17 -0700242 msg_type = col.build(col.MAGENTA, msg_type)
Simon Glassd2d136c2020-05-06 16:29:08 -0600243 line_str = '' if line is None else '%d' % line
244 return '%s:%s: %s: %s\n' % (fname, line_str, msg_type, msg)
Simon Glass26132882012-01-14 15:12:45 +0000245
Simon Glass5750cc22025-05-07 18:02:47 +0200246def check_patches(verbose, args, use_tree, cwd):
Simon Glass26132882012-01-14 15:12:45 +0000247 '''Run the checkpatch.pl script on each patch'''
Simon Glass0495abf2013-03-26 13:09:39 +0000248 error_count, warning_count, check_count = 0, 0, 0
Simon Glass26132882012-01-14 15:12:45 +0000249 col = terminal.Color()
250
Simon Glassdc19cee2023-03-08 10:52:55 -0800251 with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
252 futures = []
253 for fname in args:
Simon Glass5750cc22025-05-07 18:02:47 +0200254 f = executor.submit(check_patch, fname, verbose, use_tree=use_tree,
255 cwd=cwd)
Simon Glassdc19cee2023-03-08 10:52:55 -0800256 futures.append(f)
257
258 for fname, f in zip(args, futures):
259 result = f.result()
260 if not result.ok:
261 error_count += result.errors
262 warning_count += result.warnings
263 check_count += result.checks
264 print('%d errors, %d warnings, %d checks for %s:' % (result.errors,
265 result.warnings, result.checks, col.build(col.BLUE, fname)))
266 if (len(result.problems) != result.errors + result.warnings +
267 result.checks):
268 print("Internal error: some problems lost")
269 # Python seems to get confused by this
270 # pylint: disable=E1133
271 for item in result.problems:
272 sys.stderr.write(
273 get_warning_msg(col, item.get('type', '<unknown>'),
274 item.get('file', '<unknown>'),
275 item.get('line', 0), item.get('msg', 'message')))
276 print
Simon Glass0495abf2013-03-26 13:09:39 +0000277 if error_count or warning_count or check_count:
278 str = 'checkpatch.pl found %d error(s), %d warning(s), %d checks(s)'
Simon Glass26132882012-01-14 15:12:45 +0000279 color = col.GREEN
280 if warning_count:
281 color = col.YELLOW
282 if error_count:
283 color = col.RED
Simon Glassf45d3742022-01-29 14:14:17 -0700284 print(col.build(color, str % (error_count, warning_count, check_count)))
Simon Glass26132882012-01-14 15:12:45 +0000285 return False
286 return True