blob: f9204a907efd69b1ef3296baef22ceea33bf9064 [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 Glass0e43cad2025-05-10 13:04:55 +020025 top_level = gitutil.get_top_level() or ''
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 Glass0e43cad2025-05-10 13:04:55 +0200222 output = command.output(
223 *args, os.path.join(cwd or '', fname), raise_on_error=False,
224 capture_stderr=not use_tree)
Evan Bennec7378b2021-04-01 13:49:30 +1100225
Simon Glassd84e84a2022-01-29 14:14:06 -0700226 return check_patch_parse(output, verbose)
Evan Bennec7378b2021-04-01 13:49:30 +1100227
228
Simon Glassd84e84a2022-01-29 14:14:06 -0700229def get_warning_msg(col, msg_type, fname, line, msg):
Simon Glass26132882012-01-14 15:12:45 +0000230 '''Create a message for a given file/line
231
232 Args:
233 msg_type: Message type ('error' or 'warning')
234 fname: Filename which reports the problem
235 line: Line number where it was noticed
236 msg: Message to report
237 '''
238 if msg_type == 'warning':
Simon Glassf45d3742022-01-29 14:14:17 -0700239 msg_type = col.build(col.YELLOW, msg_type)
Simon Glass26132882012-01-14 15:12:45 +0000240 elif msg_type == 'error':
Simon Glassf45d3742022-01-29 14:14:17 -0700241 msg_type = col.build(col.RED, msg_type)
Simon Glass0495abf2013-03-26 13:09:39 +0000242 elif msg_type == 'check':
Simon Glassf45d3742022-01-29 14:14:17 -0700243 msg_type = col.build(col.MAGENTA, msg_type)
Simon Glassd2d136c2020-05-06 16:29:08 -0600244 line_str = '' if line is None else '%d' % line
245 return '%s:%s: %s: %s\n' % (fname, line_str, msg_type, msg)
Simon Glass26132882012-01-14 15:12:45 +0000246
Simon Glass5750cc22025-05-07 18:02:47 +0200247def check_patches(verbose, args, use_tree, cwd):
Simon Glass26132882012-01-14 15:12:45 +0000248 '''Run the checkpatch.pl script on each patch'''
Simon Glass0495abf2013-03-26 13:09:39 +0000249 error_count, warning_count, check_count = 0, 0, 0
Simon Glass26132882012-01-14 15:12:45 +0000250 col = terminal.Color()
251
Simon Glassdc19cee2023-03-08 10:52:55 -0800252 with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
253 futures = []
254 for fname in args:
Simon Glass5750cc22025-05-07 18:02:47 +0200255 f = executor.submit(check_patch, fname, verbose, use_tree=use_tree,
256 cwd=cwd)
Simon Glassdc19cee2023-03-08 10:52:55 -0800257 futures.append(f)
258
259 for fname, f in zip(args, futures):
260 result = f.result()
261 if not result.ok:
262 error_count += result.errors
263 warning_count += result.warnings
264 check_count += result.checks
265 print('%d errors, %d warnings, %d checks for %s:' % (result.errors,
266 result.warnings, result.checks, col.build(col.BLUE, fname)))
267 if (len(result.problems) != result.errors + result.warnings +
268 result.checks):
269 print("Internal error: some problems lost")
270 # Python seems to get confused by this
271 # pylint: disable=E1133
272 for item in result.problems:
273 sys.stderr.write(
274 get_warning_msg(col, item.get('type', '<unknown>'),
275 item.get('file', '<unknown>'),
276 item.get('line', 0), item.get('msg', 'message')))
277 print
Simon Glass0495abf2013-03-26 13:09:39 +0000278 if error_count or warning_count or check_count:
279 str = 'checkpatch.pl found %d error(s), %d warning(s), %d checks(s)'
Simon Glass26132882012-01-14 15:12:45 +0000280 color = col.GREEN
281 if warning_count:
282 color = col.YELLOW
283 if error_count:
284 color = col.RED
Simon Glassf45d3742022-01-29 14:14:17 -0700285 print(col.build(color, str % (error_count, warning_count, check_count)))
Simon Glass26132882012-01-14 15:12:45 +0000286 return False
287 return True