2 # Copyright (c) 2016, 2017 Red Hat, Inc.
3 # Copyright (c) 2018 Nicira, Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 from __future__
import print_function
26 print_file_name
= None
46 def get_yellow_begin():
53 def print_error(message
):
55 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message
))
57 __errors
= __errors
+ 1
60 def print_warning(message
):
62 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message
))
64 __warnings
= __warnings
+ 1
68 global __errors
, __warnings
, total_line
75 # These are keywords whose names are normally followed by a space and
76 # something in parentheses (usually an expression) then a left curly brace.
78 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
79 __parenthesized_constructs
= 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
81 __regex_added_line
= re
.compile(r
'^\+{1,2}[^\+][\w\W]*')
82 __regex_subtracted_line
= re
.compile(r
'^\-{1,2}[^\-][\w\W]*')
83 __regex_leading_with_whitespace_at_all
= re
.compile(r
'^\s+')
84 __regex_leading_with_spaces
= re
.compile(r
'^ +[\S]+')
85 __regex_trailing_whitespace
= re
.compile(r
'[^\S]+$')
86 __regex_single_line_feed
= re
.compile(r
'^\f$')
87 __regex_for_if_missing_whitespace
= re
.compile(r
' +(%s)[\(]'
88 % __parenthesized_constructs
)
89 __regex_for_if_too_much_whitespace
= re
.compile(r
' +(%s) +[\(]'
90 % __parenthesized_constructs
)
91 __regex_for_if_parens_whitespace
= \
92 re
.compile(r
' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs
)
93 __regex_is_for_if_single_line_bracket
= \
94 re
.compile(r
'^ +(%s) \(.*\)' % __parenthesized_constructs
)
95 __regex_ends_with_bracket
= \
96 re
.compile(r
'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
97 __regex_ptr_declaration_missing_whitespace
= re
.compile(r
'[a-zA-Z0-9]\*[^*]')
98 __regex_is_comment_line
= re
.compile(r
'^\s*(/\*|\*\s)')
99 __regex_has_comment
= re
.compile(r
'.*(/\*|\*\s)')
100 __regex_trailing_operator
= re
.compile(r
'^[^ ]* [^ ]*[?:]$')
101 __regex_conditional_else_bracing
= re
.compile(r
'^\s*else\s*{?$')
102 __regex_conditional_else_bracing2
= re
.compile(r
'^\s*}\selse\s*$')
103 __regex_has_xxx_mark
= re
.compile(r
'.*xxx.*', re
.IGNORECASE
)
105 skip_leading_whitespace_check
= False
106 skip_trailing_whitespace_check
= False
107 skip_block_whitespace_check
= False
108 skip_signoff_check
= False
110 # Don't enforce character limit on files that include these characters in their
111 # name, as they may have legitimate reasons to have longer lines.
113 # Python isn't checked as flake8 performs these checks during build.
114 line_length_blacklist
= ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
117 # Don't enforce a requirement that leading whitespace be all spaces on
118 # files that include these characters in their name, since these kinds
119 # of files need lines with leading tabs.
120 leading_whitespace_blacklist
= ['.mk', '.am', '.at']
123 def is_subtracted_line(line
):
124 """Returns TRUE if the line in question has been removed."""
125 return __regex_subtracted_line
.search(line
) is not None
128 def is_added_line(line
):
129 """Returns TRUE if the line in question is an added line.
132 return __regex_added_line
.search(line
) is not None or checking_file
135 def added_line(line
):
136 """Returns the line formatted properly by removing diff syntax"""
138 if not checking_file
:
143 def leading_whitespace_is_spaces(line
):
144 """Returns TRUE if the leading whitespace in added lines is spaces
146 if skip_leading_whitespace_check
:
148 if (__regex_leading_with_whitespace_at_all
.search(line
) is not None and
149 __regex_single_line_feed
.search(line
) is None):
150 return __regex_leading_with_spaces
.search(line
) is not None
155 def trailing_whitespace_or_crlf(line
):
156 """Returns TRUE if the trailing characters is whitespace
158 if skip_trailing_whitespace_check
:
160 return (__regex_trailing_whitespace
.search(line
) is not None and
161 __regex_single_line_feed
.search(line
) is None)
164 def if_and_for_whitespace_checks(line
):
165 """Return TRUE if there is appropriate whitespace after if, for, while
167 if skip_block_whitespace_check
:
169 if (__regex_for_if_missing_whitespace
.search(line
) is not None or
170 __regex_for_if_too_much_whitespace
.search(line
) is not None or
171 __regex_for_if_parens_whitespace
.search(line
)):
176 def if_and_for_end_with_bracket_check(line
):
177 """Return TRUE if there is not a bracket at the end of an if, for, while
178 block which fits on a single line ie: 'if (foo)'"""
180 def balanced_parens(line
):
181 """This is a rather naive counter - it won't deal with quotes"""
190 if __regex_is_for_if_single_line_bracket
.search(line
) is not None:
191 if not balanced_parens(line
):
193 if __regex_ends_with_bracket
.search(line
) is None:
195 if __regex_conditional_else_bracing
.match(line
) is not None:
197 if __regex_conditional_else_bracing2
.match(line
) is not None:
202 def pointer_whitespace_check(line
):
203 """Return TRUE if there is no space between a pointer name and the
204 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
205 return __regex_ptr_declaration_missing_whitespace
.search(line
) is not None
208 def line_length_check(line
):
209 """Return TRUE if the line length is too long"""
215 def is_comment_line(line
):
216 """Returns TRUE if the current line is part of a block comment."""
217 return __regex_is_comment_line
.match(line
) is not None
220 def has_comment(line
):
221 """Returns TRUE if the current line contains a comment or is part of
223 return __regex_has_comment
.match(line
) is not None
226 def trailing_operator(line
):
227 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
228 return __regex_trailing_operator
.match(line
) is not None
231 def has_xxx_mark(line
):
232 """Returns TRUE if the current line contains 'xxx'."""
233 return __regex_has_xxx_mark
.match(line
) is not None
239 lambda x
: not any([fmt
in x
for fmt
in line_length_blacklist
]),
240 'check': lambda x
: line_length_check(x
),
241 'print': lambda: print_warning("Line length is >79-characters long")},
245 lambda x
: not any([fmt
in x
for fmt
in leading_whitespace_blacklist
]),
246 'check': lambda x
: not leading_whitespace_is_spaces(x
),
247 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
249 {'regex': None, 'match_name': None,
250 'check': lambda x
: trailing_whitespace_or_crlf(x
),
251 'print': lambda: print_warning("Line has trailing whitespace")},
253 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
254 'prereq': lambda x
: not is_comment_line(x
),
255 'check': lambda x
: not if_and_for_whitespace_checks(x
),
256 'print': lambda: print_error("Improper whitespace around control block")},
258 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
259 'prereq': lambda x
: not is_comment_line(x
),
260 'check': lambda x
: not if_and_for_end_with_bracket_check(x
),
261 'print': lambda: print_error("Inappropriate bracing around statement")},
263 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
264 'prereq': lambda x
: not is_comment_line(x
),
265 'check': lambda x
: pointer_whitespace_check(x
),
267 lambda: print_error("Inappropriate spacing in pointer declaration")},
269 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
270 'prereq': lambda x
: not is_comment_line(x
),
271 'check': lambda x
: trailing_operator(x
),
273 lambda: print_error("Line has '?' or ':' operator at end of line")},
275 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
276 'prereq': lambda x
: has_comment(x
),
277 'check': lambda x
: has_xxx_mark(x
),
278 'print': lambda: print_warning("Comment with 'xxx' marker")},
282 def regex_function_factory(func_name
):
283 regex
= re
.compile(r
'\b%s\([^)]*\)' % func_name
)
284 return lambda x
: regex
.search(x
) is not None
287 def regex_error_factory(description
):
288 return lambda: print_error(description
)
292 ('malloc', 'Use xmalloc() in place of malloc()'),
293 ('calloc', 'Use xcalloc() in place of calloc()'),
294 ('realloc', 'Use xrealloc() in place of realloc()'),
295 ('strdup', 'Use xstrdup() in place of strdup()'),
296 ('asprintf', 'Use xasprintf() in place of asprintf()'),
297 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
298 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
299 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
300 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
301 ('strerror', 'Use ovs_strerror() in place of strerror()'),
302 ('sleep', 'Use xsleep() in place of sleep()'),
303 ('abort', 'Use ovs_abort() in place of abort()'),
304 ('assert', 'Use ovs_assert() in place of assert()'),
305 ('error', 'Use ovs_error() in place of error()'),
308 {'regex': '(\.c|\.h)(\.in)?$',
310 'prereq': lambda x
: not is_comment_line(x
),
311 'check': regex_function_factory(function_name
),
312 'print': regex_error_factory(description
)}
313 for (function_name
, description
) in std_functions
]
316 def regex_operator_factory(operator
):
317 regex
= re
.compile(r
'^[^#][^"\']*[^
"]%s[^ "\'][^
"]*' % operator)
318 return lambda x: regex.search(x) is not None
322 [re.escape(op) for op in ['/', '%', '<<', '>>', '<=', '>=', '==', '!=',
323 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
324 '&=', '^=', '|=', '<<=', '>>=']] \
325 + ['[^<" ]<[^
=" ]', '[^->" ]>[^
=" ]', '[^ !()/"]\
*[^
/]', '[^
!&()"]&',
326 '[^" +(]\
+[^
"+;]', '[^" -(]-[^
"->;]', '[^" <>=!^|
+\
-*/%&]=[^
"=]']
328 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
329 'prereq': lambda x: not is_comment_line(x),
330 'check': regex_operator_factory(operator),
331 'print': lambda: print_warning("Line lacks whitespace around operator
")}
332 for operator in infix_operators]
335 def get_file_type_checks(filename):
336 """Returns the list of checks for a file based on matching the filename
341 if check['regex'] is None and check['match_name'] is None:
342 checkList.append(check)
343 if check['regex'] is not None and \
344 re.compile(check['regex']).search(filename) is not None:
345 checkList.append(check)
346 elif check['match_name'] is not None and check['match_name'](filename):
347 checkList.append(check)
351 def run_checks(current_file, line, lineno):
352 """Runs the various checks for the particular line. This will take
353 filename into account."""
354 global checking_file, total_line
356 for check in get_file_type_checks(current_file):
357 if 'prereq' in check and not check['prereq'](line):
359 if check['check'](line):
365 print("%s:%d:" % (current_file, lineno))
367 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
371 def ovs_checkpatch_parse(text
, filename
):
372 global print_file_name
, total_line
, checking_file
377 current_file
= filename
if checking_file
else ''
379 scissors
= re
.compile(r
'^[\w]*---[\w]*')
380 hunks
= re
.compile('^(---|\+\+\+) (\S+)')
381 hunk_differences
= re
.compile(
382 r
'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
383 is_signature
= re
.compile(r
'((\s*Signed-off-by: )(.*))$',
385 is_co_author
= re
.compile(r
'(\s*(Co-authored-by: )(.*))$',
387 is_gerrit_change_id
= re
.compile(r
'(\s*(change-id: )(.*))$',
392 for line
in text
.split('\n'):
393 if current_file
!= previous_file
:
394 previous_file
= current_file
397 total_line
= total_line
+ 1
405 match
= hunks
.match(line
)
408 current_file
= match
.group(2)[2:]
409 print_file_name
= current_file
412 if scissors
.match(line
):
414 if not skip_signoff_check
:
415 if len(signatures
) == 0:
416 print_error("No signatures found.")
417 elif len(signatures
) != 1 + len(co_authors
):
418 print_error("Too many signoffs; "
419 "are you missing Co-authored-by lines?")
420 if not set(co_authors
) <= set(signatures
):
421 print_error("Co-authored-by/Signed-off-by corruption")
422 elif is_signature
.match(line
):
423 m
= is_signature
.match(line
)
424 signatures
.append(m
.group(3))
425 elif is_co_author
.match(line
):
426 m
= is_co_author
.match(line
)
427 co_authors
.append(m
.group(3))
428 elif is_gerrit_change_id
.match(line
):
430 "Remove Gerrit Change-Id's before submitting upstream.")
431 print("%d: %s\n" % (lineno
, line
))
433 newfile
= hunks
.match(line
)
435 current_file
= newfile
.group(2)[2:]
436 print_file_name
= current_file
438 reset_line_number
= hunk_differences
.match(line
)
439 if reset_line_number
:
440 lineno
= int(reset_line_number
.group(3))
444 if is_subtracted_line(line
):
446 if not is_added_line(line
):
449 cmp_line
= added_line(line
)
451 # Skip files which have /datapath in them, since they are
452 # linux or windows coding standards
453 if current_file
.startswith('datapath'):
455 if current_file
.startswith('include/linux'):
457 run_checks(current_file
, cmp_line
, lineno
)
458 if __errors
or __warnings
:
465 Open vSwitch checkpatch.py
466 Checks a patch for trivial mistakes.
468 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
471 -f|--check-file Arguments are source files, not patches.
472 -1, -2, ... Check recent commits in this repo.
475 -h|--help This help message
476 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
477 -l|--skip-leading-whitespace Skips the leading whitespace test
478 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
479 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
483 def ovs_checkpatch_print_result(result
):
484 global __warnings
, __errors
, total_line
486 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
487 (total_line
, __warnings
, __errors
))
489 print("Lines checked: %d, no obvious problems found\n" % (total_line
))
492 def ovs_checkpatch_file(filename
):
494 mail
= email
.message_from_file(open(filename
, 'r'))
496 print_error("Unable to parse file '%s'. Is it a patch?" % filename
)
499 for part
in mail
.walk():
500 if part
.get_content_maintype() == 'multipart':
502 result
= ovs_checkpatch_parse(part
.get_payload(decode
=False), filename
)
503 ovs_checkpatch_print_result(result
)
507 def partition(pred
, iterable
):
508 """Returns [[trues], [falses]], where [trues] is the items in
509 'iterable' that satisfy 'pred' and [falses] is all the rest."""
512 for item
in iterable
:
520 if __name__
== '__main__':
522 numeric_options
, args
= partition(lambda s
: re
.match('-[0-9]+$', s
),
524 n_patches
= int(numeric_options
[-1][1:]) if numeric_options
else 0
526 optlist
, args
= getopt
.getopt(args
, 'bhlstf',
529 "skip-block-whitespace",
530 "skip-leading-whitespace",
531 "skip-signoff-lines",
532 "skip-trailing-whitespace"])
534 print("Unknown option encountered. Please rerun with -h for help.")
538 if o
in ("-h", "--help"):
541 elif o
in ("-b", "--skip-block-whitespace"):
542 skip_block_whitespace_check
= True
543 elif o
in ("-l", "--skip-leading-whitespace"):
544 skip_leading_whitespace_check
= True
545 elif o
in ("-s", "--skip-signoff-lines"):
546 skip_signoff_check
= True
547 elif o
in ("-t", "--skip-trailing-whitespace"):
548 skip_trailing_whitespace_check
= True
549 elif o
in ("-f", "--check-file"):
552 print("Unknown option '%s'" % o
)
555 if sys
.stdout
.isatty():
561 git_log
= 'git log --no-color --no-merges --pretty=format:"%H %s" '
562 with os
.popen(git_log
+ '-%d' % n_patches
, 'r') as f
:
563 commits
= f
.read().split("\n")
565 for i
in reversed(range(0, n_patches
)):
566 revision
, name
= commits
[i
].split(" ", 1)
567 f
= os
.popen('git format-patch -1 --stdout %s' % revision
, 'r')
571 print('== Checking %s ("%s") ==' % (revision
[0:12], name
))
572 result
= ovs_checkpatch_parse(patch
, revision
)
573 ovs_checkpatch_print_result(result
)
579 if sys
.stdin
.isatty():
582 result
= ovs_checkpatch_parse(sys
.stdin
.read(), '-')
583 ovs_checkpatch_print_result(result
)
587 for filename
in args
:
588 print('== Checking "%s" ==' % filename
)
589 result
= ovs_checkpatch_file(filename
)