2 # Copyright (c) 2016, 2017 Red Hat, Inc.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 from __future__
import print_function
25 print_file_name
= None
45 def get_yellow_begin():
52 def print_error(message
):
54 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message
))
56 __errors
= __errors
+ 1
59 def print_warning(message
):
61 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message
))
63 __warnings
= __warnings
+ 1
66 # These are keywords whose names are normally followed by a space and
67 # something in parentheses (usually an expression) then a left curly brace.
69 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
70 __parenthesized_constructs
= 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
72 __regex_added_line
= re
.compile(r
'^\+{1,2}[^\+][\w\W]*')
73 __regex_subtracted_line
= re
.compile(r
'^\-{1,2}[^\-][\w\W]*')
74 __regex_leading_with_whitespace_at_all
= re
.compile(r
'^\s+')
75 __regex_leading_with_spaces
= re
.compile(r
'^ +[\S]+')
76 __regex_trailing_whitespace
= re
.compile(r
'[^\S]+$')
77 __regex_single_line_feed
= re
.compile(r
'^\f$')
78 __regex_for_if_missing_whitespace
= re
.compile(r
' +(%s)[\(]'
79 % __parenthesized_constructs
)
80 __regex_for_if_too_much_whitespace
= re
.compile(r
' +(%s) +[\(]'
81 % __parenthesized_constructs
)
82 __regex_for_if_parens_whitespace
= \
83 re
.compile(r
' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs
)
84 __regex_is_for_if_single_line_bracket
= \
85 re
.compile(r
'^ +(%s) \(.*\)' % __parenthesized_constructs
)
86 __regex_ends_with_bracket
= \
87 re
.compile(r
'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
88 __regex_ptr_declaration_missing_whitespace
= re
.compile(r
'[a-zA-Z0-9]\*[^*]')
89 __regex_is_comment_line
= re
.compile(r
'^\s*(/\*|\*\s)')
91 skip_leading_whitespace_check
= False
92 skip_trailing_whitespace_check
= False
93 skip_block_whitespace_check
= False
94 skip_signoff_check
= False
96 # Don't enforce character limit on files that include these characters in their
97 # name, as they may have legitimate reasons to have longer lines.
99 # Python isn't checked as flake8 performs these checks during build.
100 line_length_blacklist
= ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
103 # Don't enforce a requirement that leading whitespace be all spaces on
104 # files that include these characters in their name, since these kinds
105 # of files need lines with leading tabs.
106 leading_whitespace_blacklist
= ['.mk', '.am', '.at']
109 def is_subtracted_line(line
):
110 """Returns TRUE if the line in question has been removed."""
111 return __regex_subtracted_line
.search(line
) is not None
114 def is_added_line(line
):
115 """Returns TRUE if the line in question is an added line.
118 return __regex_added_line
.search(line
) is not None or checking_file
121 def added_line(line
):
122 """Returns the line formatted properly by removing diff syntax"""
124 if not checking_file
:
129 def leading_whitespace_is_spaces(line
):
130 """Returns TRUE if the leading whitespace in added lines is spaces
132 if skip_leading_whitespace_check
:
134 if (__regex_leading_with_whitespace_at_all
.search(line
) is not None and
135 __regex_single_line_feed
.search(line
) is None):
136 return __regex_leading_with_spaces
.search(line
) is not None
141 def trailing_whitespace_or_crlf(line
):
142 """Returns TRUE if the trailing characters is whitespace
144 if skip_trailing_whitespace_check
:
146 return (__regex_trailing_whitespace
.search(line
) is not None and
147 __regex_single_line_feed
.search(line
) is None)
150 def if_and_for_whitespace_checks(line
):
151 """Return TRUE if there is appropriate whitespace after if, for, while
153 if skip_block_whitespace_check
:
155 if (__regex_for_if_missing_whitespace
.search(line
) is not None or
156 __regex_for_if_too_much_whitespace
.search(line
) is not None or
157 __regex_for_if_parens_whitespace
.search(line
)):
162 def if_and_for_end_with_bracket_check(line
):
163 """Return TRUE if there is not a bracket at the end of an if, for, while
164 block which fits on a single line ie: 'if (foo)'"""
166 def balanced_parens(line
):
167 """This is a rather naive counter - it won't deal with quotes"""
176 if __regex_is_for_if_single_line_bracket
.search(line
) is not None:
177 if not balanced_parens(line
):
179 if __regex_ends_with_bracket
.search(line
) is None:
184 def pointer_whitespace_check(line
):
185 """Return TRUE if there is no space between a pointer name and the
186 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
187 return __regex_ptr_declaration_missing_whitespace
.search(line
) is not None
190 def line_length_check(line
):
191 """Return TRUE if the line length is too long"""
197 def is_comment_line(line
):
198 """Returns TRUE if the current line is part of a block comment."""
199 return __regex_is_comment_line
.match(line
) is not None
205 lambda x
: not any([fmt
in x
for fmt
in line_length_blacklist
]),
206 'check': lambda x
: line_length_check(x
),
207 'print': lambda: print_warning("Line length is >79-characters long")},
211 lambda x
: not any([fmt
in x
for fmt
in leading_whitespace_blacklist
]),
212 'check': lambda x
: not leading_whitespace_is_spaces(x
),
213 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
215 {'regex': None, 'match_name': None,
216 'check': lambda x
: trailing_whitespace_or_crlf(x
),
217 'print': lambda: print_warning("Line has trailing whitespace")},
219 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
220 'prereq': lambda x
: not is_comment_line(x
),
221 'check': lambda x
: not if_and_for_whitespace_checks(x
),
222 'print': lambda: print_error("Improper whitespace around control block")},
224 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
225 'prereq': lambda x
: not is_comment_line(x
),
226 'check': lambda x
: not if_and_for_end_with_bracket_check(x
),
227 'print': lambda: print_error("Inappropriate bracing around statement")},
229 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
230 'prereq': lambda x
: not is_comment_line(x
),
231 'check': lambda x
: pointer_whitespace_check(x
),
233 lambda: print_error("Inappropriate spacing in pointer declaration")}
237 def regex_function_factory(func_name
):
238 regex
= re
.compile(r
'\b%s\([^)]*\)' % func_name
)
239 return lambda x
: regex
.search(x
) is not None
242 def regex_error_factory(description
):
243 return lambda: print_error(description
)
247 ('malloc', 'Use xmalloc() in place of malloc()'),
248 ('calloc', 'Use xcalloc() in place of calloc()'),
249 ('realloc', 'Use xrealloc() in place of realloc()'),
250 ('strdup', 'Use xstrdup() in place of strdup()'),
251 ('asprintf', 'Use xasprintf() in place of asprintf()'),
252 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
253 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
254 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
255 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
256 ('strerror', 'Use ovs_strerror() in place of strerror()'),
257 ('sleep', 'Use xsleep() in place of sleep()'),
258 ('abort', 'Use ovs_abort() in place of abort()'),
259 ('error', 'Use ovs_error() in place of error()'),
262 {'regex': '(.c|.h)(.in)?$',
264 'prereq': lambda x
: not is_comment_line(x
),
265 'check': regex_function_factory(function_name
),
266 'print': regex_error_factory(description
)}
267 for (function_name
, description
) in std_functions
]
270 def get_file_type_checks(filename
):
271 """Returns the list of checks for a file based on matching the filename
276 if check
['regex'] is None and check
['match_name'] is None:
277 checkList
.append(check
)
278 if check
['regex'] is not None and \
279 re
.compile(check
['regex']).search(filename
) is not None:
280 checkList
.append(check
)
281 elif check
['match_name'] is not None and check
['match_name'](filename
):
282 checkList
.append(check
)
286 def run_checks(current_file
, line
, lineno
):
287 """Runs the various checks for the particular line. This will take
288 filename into account."""
289 global checking_file
, total_line
291 for check
in get_file_type_checks(current_file
):
292 if 'prereq' in check
and not check
['prereq'](line
):
294 if check
['check'](line
):
300 print("%s:%d:" % (current_file
, lineno
))
302 print("#%d FILE: %s:%d:" % (total_line
, current_file
, lineno
))
306 def ovs_checkpatch_parse(text
, filename
):
307 global print_file_name
, total_line
, checking_file
312 current_file
= filename
if checking_file
else ''
314 scissors
= re
.compile(r
'^[\w]*---[\w]*')
315 hunks
= re
.compile('^(---|\+\+\+) (\S+)')
316 hunk_differences
= re
.compile(
317 r
'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
318 is_signature
= re
.compile(r
'((\s*Signed-off-by: )(.*))$',
320 is_co_author
= re
.compile(r
'(\s*(Co-authored-by: )(.*))$',
323 for line
in text
.split('\n'):
324 if current_file
!= previous_file
:
325 previous_file
= current_file
328 total_line
= total_line
+ 1
336 match
= hunks
.match(line
)
339 current_file
= match
.group(2)[2:]
340 print_file_name
= current_file
343 if scissors
.match(line
):
345 if not skip_signoff_check
:
346 if len(signatures
) == 0:
347 print_error("No signatures found.")
348 elif len(signatures
) != 1 + len(co_authors
):
349 print_error("Too many signoffs; "
350 "are you missing Co-authored-by lines?")
351 if not set(co_authors
) <= set(signatures
):
352 print_error("Co-authored-by/Signed-off-by corruption")
353 elif is_signature
.match(line
):
354 m
= is_signature
.match(line
)
355 signatures
.append(m
.group(3))
356 elif is_co_author
.match(line
):
357 m
= is_co_author
.match(line
)
358 co_authors
.append(m
.group(3))
360 newfile
= hunks
.match(line
)
362 current_file
= newfile
.group(2)[2:]
363 print_file_name
= current_file
365 reset_line_number
= hunk_differences
.match(line
)
366 if reset_line_number
:
367 lineno
= int(reset_line_number
.group(3))
371 if is_subtracted_line(line
):
373 if not is_added_line(line
):
376 cmp_line
= added_line(line
)
378 # Skip files which have /datapath in them, since they are
379 # linux or windows coding standards
380 if current_file
.startswith('datapath'):
382 if current_file
.startswith('include/linux'):
384 run_checks(current_file
, cmp_line
, lineno
)
385 if __errors
or __warnings
:
392 Open vSwitch checkpatch.py
393 Checks a patch for trivial mistakes.
395 %s [options] [PATCH | -f SOURCE | -1 | -2 | ...]
398 -f|--check-file Arguments are source files, not patches.
399 -1, -2, ... Check recent commits in this repo.
402 -h|--help This help message
403 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
404 -l|--skip-leading-whitespace Skips the leading whitespace test
405 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
406 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
410 def ovs_checkpatch_file(filename
):
411 global __warnings
, __errors
, checking_file
, total_line
413 mail
= email
.message_from_file(open(filename
, 'r'))
415 print_error("Unable to parse file '%s'. Is it a patch?" % filename
)
418 for part
in mail
.walk():
419 if part
.get_content_maintype() == 'multipart':
421 result
= ovs_checkpatch_parse(part
.get_payload(decode
=False), filename
)
423 print("Lines checked: %d, Warnings: %d, Errors: %d" %
424 (total_line
, __warnings
, __errors
))
426 print("Lines checked: %d, no obvious problems found" % (total_line
))
430 def partition(pred
, iterable
):
431 """Returns [[trues], [falses]], where [trues] is the items in
432 'iterable' that satisfy 'pred' and [falses] is all the rest."""
435 for item
in iterable
:
443 if __name__
== '__main__':
445 numeric_options
, args
= partition(lambda s
: re
.match('-[0-9]+$', s
),
447 n_patches
= int(numeric_options
[-1][1:]) if numeric_options
else 0
449 optlist
, args
= getopt
.getopt(args
, 'bhlstf',
452 "skip-block-whitespace",
453 "skip-leading-whitespace",
454 "skip-signoff-lines",
455 "skip-trailing-whitespace"])
457 print("Unknown option encountered. Please rerun with -h for help.")
461 if o
in ("-h", "--help"):
464 elif o
in ("-b", "--skip-block-whitespace"):
465 skip_block_whitespace_check
= True
466 elif o
in ("-l", "--skip-leading-whitespace"):
467 skip_leading_whitespace_check
= True
468 elif o
in ("-s", "--skip-signoff-lines"):
469 skip_signoff_check
= True
470 elif o
in ("-t", "--skip-trailing-whitespace"):
471 skip_trailing_whitespace_check
= True
472 elif o
in ("-f", "--check-file"):
475 print("Unknown option '%s'" % o
)
478 if sys
.stdout
.isatty():
483 for i
in reversed(range(0, n_patches
)):
484 revision
= 'HEAD~%d' % i
485 f
= os
.popen('git format-patch -1 --stdout %s' % revision
, 'r')
489 print('== Checking %s ==' % revision
)
490 if ovs_checkpatch_parse(patch
, revision
):
497 if sys
.stdin
.isatty():
500 sys
.exit(ovs_checkpatch_parse(sys
.stdin
.read(), '-'))
501 sys
.exit(ovs_checkpatch_file(filename
))