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
67 global __errors
, __warnings
73 # These are keywords whose names are normally followed by a space and
74 # something in parentheses (usually an expression) then a left curly brace.
76 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
77 __parenthesized_constructs
= 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
79 __regex_added_line
= re
.compile(r
'^\+{1,2}[^\+][\w\W]*')
80 __regex_subtracted_line
= re
.compile(r
'^\-{1,2}[^\-][\w\W]*')
81 __regex_leading_with_whitespace_at_all
= re
.compile(r
'^\s+')
82 __regex_leading_with_spaces
= re
.compile(r
'^ +[\S]+')
83 __regex_trailing_whitespace
= re
.compile(r
'[^\S]+$')
84 __regex_single_line_feed
= re
.compile(r
'^\f$')
85 __regex_for_if_missing_whitespace
= re
.compile(r
' +(%s)[\(]'
86 % __parenthesized_constructs
)
87 __regex_for_if_too_much_whitespace
= re
.compile(r
' +(%s) +[\(]'
88 % __parenthesized_constructs
)
89 __regex_for_if_parens_whitespace
= \
90 re
.compile(r
' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs
)
91 __regex_is_for_if_single_line_bracket
= \
92 re
.compile(r
'^ +(%s) \(.*\)' % __parenthesized_constructs
)
93 __regex_ends_with_bracket
= \
94 re
.compile(r
'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
95 __regex_ptr_declaration_missing_whitespace
= re
.compile(r
'[a-zA-Z0-9]\*[^*]')
96 __regex_is_comment_line
= re
.compile(r
'^\s*(/\*|\*\s)')
98 skip_leading_whitespace_check
= False
99 skip_trailing_whitespace_check
= False
100 skip_block_whitespace_check
= False
101 skip_signoff_check
= False
103 # Don't enforce character limit on files that include these characters in their
104 # name, as they may have legitimate reasons to have longer lines.
106 # Python isn't checked as flake8 performs these checks during build.
107 line_length_blacklist
= ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
110 # Don't enforce a requirement that leading whitespace be all spaces on
111 # files that include these characters in their name, since these kinds
112 # of files need lines with leading tabs.
113 leading_whitespace_blacklist
= ['.mk', '.am', '.at']
116 def is_subtracted_line(line
):
117 """Returns TRUE if the line in question has been removed."""
118 return __regex_subtracted_line
.search(line
) is not None
121 def is_added_line(line
):
122 """Returns TRUE if the line in question is an added line.
125 return __regex_added_line
.search(line
) is not None or checking_file
128 def added_line(line
):
129 """Returns the line formatted properly by removing diff syntax"""
131 if not checking_file
:
136 def leading_whitespace_is_spaces(line
):
137 """Returns TRUE if the leading whitespace in added lines is spaces
139 if skip_leading_whitespace_check
:
141 if (__regex_leading_with_whitespace_at_all
.search(line
) is not None and
142 __regex_single_line_feed
.search(line
) is None):
143 return __regex_leading_with_spaces
.search(line
) is not None
148 def trailing_whitespace_or_crlf(line
):
149 """Returns TRUE if the trailing characters is whitespace
151 if skip_trailing_whitespace_check
:
153 return (__regex_trailing_whitespace
.search(line
) is not None and
154 __regex_single_line_feed
.search(line
) is None)
157 def if_and_for_whitespace_checks(line
):
158 """Return TRUE if there is appropriate whitespace after if, for, while
160 if skip_block_whitespace_check
:
162 if (__regex_for_if_missing_whitespace
.search(line
) is not None or
163 __regex_for_if_too_much_whitespace
.search(line
) is not None or
164 __regex_for_if_parens_whitespace
.search(line
)):
169 def if_and_for_end_with_bracket_check(line
):
170 """Return TRUE if there is not a bracket at the end of an if, for, while
171 block which fits on a single line ie: 'if (foo)'"""
173 def balanced_parens(line
):
174 """This is a rather naive counter - it won't deal with quotes"""
183 if __regex_is_for_if_single_line_bracket
.search(line
) is not None:
184 if not balanced_parens(line
):
186 if __regex_ends_with_bracket
.search(line
) is None:
191 def pointer_whitespace_check(line
):
192 """Return TRUE if there is no space between a pointer name and the
193 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
194 return __regex_ptr_declaration_missing_whitespace
.search(line
) is not None
197 def line_length_check(line
):
198 """Return TRUE if the line length is too long"""
204 def is_comment_line(line
):
205 """Returns TRUE if the current line is part of a block comment."""
206 return __regex_is_comment_line
.match(line
) is not None
212 lambda x
: not any([fmt
in x
for fmt
in line_length_blacklist
]),
213 'check': lambda x
: line_length_check(x
),
214 'print': lambda: print_warning("Line length is >79-characters long")},
218 lambda x
: not any([fmt
in x
for fmt
in leading_whitespace_blacklist
]),
219 'check': lambda x
: not leading_whitespace_is_spaces(x
),
220 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
222 {'regex': None, 'match_name': None,
223 'check': lambda x
: trailing_whitespace_or_crlf(x
),
224 'print': lambda: print_warning("Line has trailing whitespace")},
226 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
227 'prereq': lambda x
: not is_comment_line(x
),
228 'check': lambda x
: not if_and_for_whitespace_checks(x
),
229 'print': lambda: print_error("Improper whitespace around control block")},
231 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
232 'prereq': lambda x
: not is_comment_line(x
),
233 'check': lambda x
: not if_and_for_end_with_bracket_check(x
),
234 'print': lambda: print_error("Inappropriate bracing around statement")},
236 {'regex': '(.c|.h)(.in)?$', 'match_name': None,
237 'prereq': lambda x
: not is_comment_line(x
),
238 'check': lambda x
: pointer_whitespace_check(x
),
240 lambda: print_error("Inappropriate spacing in pointer declaration")}
244 def regex_function_factory(func_name
):
245 regex
= re
.compile(r
'\b%s\([^)]*\)' % func_name
)
246 return lambda x
: regex
.search(x
) is not None
249 def regex_error_factory(description
):
250 return lambda: print_error(description
)
254 ('malloc', 'Use xmalloc() in place of malloc()'),
255 ('calloc', 'Use xcalloc() in place of calloc()'),
256 ('realloc', 'Use xrealloc() in place of realloc()'),
257 ('strdup', 'Use xstrdup() in place of strdup()'),
258 ('asprintf', 'Use xasprintf() in place of asprintf()'),
259 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
260 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
261 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
262 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
263 ('strerror', 'Use ovs_strerror() in place of strerror()'),
264 ('sleep', 'Use xsleep() in place of sleep()'),
265 ('abort', 'Use ovs_abort() in place of abort()'),
266 ('assert', 'Use ovs_assert() in place of assert()'),
267 ('error', 'Use ovs_error() in place of error()'),
270 {'regex': '(.c|.h)(.in)?$',
272 'prereq': lambda x
: not is_comment_line(x
),
273 'check': regex_function_factory(function_name
),
274 'print': regex_error_factory(description
)}
275 for (function_name
, description
) in std_functions
]
278 def get_file_type_checks(filename
):
279 """Returns the list of checks for a file based on matching the filename
284 if check
['regex'] is None and check
['match_name'] is None:
285 checkList
.append(check
)
286 if check
['regex'] is not None and \
287 re
.compile(check
['regex']).search(filename
) is not None:
288 checkList
.append(check
)
289 elif check
['match_name'] is not None and check
['match_name'](filename
):
290 checkList
.append(check
)
294 def run_checks(current_file
, line
, lineno
):
295 """Runs the various checks for the particular line. This will take
296 filename into account."""
297 global checking_file
, total_line
299 for check
in get_file_type_checks(current_file
):
300 if 'prereq' in check
and not check
['prereq'](line
):
302 if check
['check'](line
):
308 print("%s:%d:" % (current_file
, lineno
))
310 print("#%d FILE: %s:%d:" % (total_line
, current_file
, lineno
))
314 def ovs_checkpatch_parse(text
, filename
):
315 global print_file_name
, total_line
, checking_file
320 current_file
= filename
if checking_file
else ''
322 scissors
= re
.compile(r
'^[\w]*---[\w]*')
323 hunks
= re
.compile('^(---|\+\+\+) (\S+)')
324 hunk_differences
= re
.compile(
325 r
'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
326 is_signature
= re
.compile(r
'((\s*Signed-off-by: )(.*))$',
328 is_co_author
= re
.compile(r
'(\s*(Co-authored-by: )(.*))$',
330 is_gerrit_change_id
= re
.compile(r
'(\s*(change-id: )(.*))$',
335 for line
in text
.split('\n'):
336 if current_file
!= previous_file
:
337 previous_file
= current_file
340 total_line
= total_line
+ 1
348 match
= hunks
.match(line
)
351 current_file
= match
.group(2)[2:]
352 print_file_name
= current_file
355 if scissors
.match(line
):
357 if not skip_signoff_check
:
358 if len(signatures
) == 0:
359 print_error("No signatures found.")
360 elif len(signatures
) != 1 + len(co_authors
):
361 print_error("Too many signoffs; "
362 "are you missing Co-authored-by lines?")
363 if not set(co_authors
) <= set(signatures
):
364 print_error("Co-authored-by/Signed-off-by corruption")
365 elif is_signature
.match(line
):
366 m
= is_signature
.match(line
)
367 signatures
.append(m
.group(3))
368 elif is_co_author
.match(line
):
369 m
= is_co_author
.match(line
)
370 co_authors
.append(m
.group(3))
371 elif is_gerrit_change_id
.match(line
):
373 "Remove Gerrit Change-Id's before submitting upstream.")
374 print("%d: %s\n" % (lineno
, line
))
376 newfile
= hunks
.match(line
)
378 current_file
= newfile
.group(2)[2:]
379 print_file_name
= current_file
381 reset_line_number
= hunk_differences
.match(line
)
382 if reset_line_number
:
383 lineno
= int(reset_line_number
.group(3))
387 if is_subtracted_line(line
):
389 if not is_added_line(line
):
392 cmp_line
= added_line(line
)
394 # Skip files which have /datapath in them, since they are
395 # linux or windows coding standards
396 if current_file
.startswith('datapath'):
398 if current_file
.startswith('include/linux'):
400 run_checks(current_file
, cmp_line
, lineno
)
401 if __errors
or __warnings
:
408 Open vSwitch checkpatch.py
409 Checks a patch for trivial mistakes.
411 %s [options] [PATCH | -f SOURCE | -1 | -2 | ...]
414 -f|--check-file Arguments are source files, not patches.
415 -1, -2, ... Check recent commits in this repo.
418 -h|--help This help message
419 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
420 -l|--skip-leading-whitespace Skips the leading whitespace test
421 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
422 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
426 def ovs_checkpatch_print_result(result
):
427 global __warnings
, __errors
, total_line
429 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
430 (total_line
, __warnings
, __errors
))
432 print("Lines checked: %d, no obvious problems found\n" % (total_line
))
435 def ovs_checkpatch_file(filename
):
437 mail
= email
.message_from_file(open(filename
, 'r'))
439 print_error("Unable to parse file '%s'. Is it a patch?" % filename
)
442 for part
in mail
.walk():
443 if part
.get_content_maintype() == 'multipart':
445 result
= ovs_checkpatch_parse(part
.get_payload(decode
=False), filename
)
446 ovs_checkpatch_print_result(result
)
450 def partition(pred
, iterable
):
451 """Returns [[trues], [falses]], where [trues] is the items in
452 'iterable' that satisfy 'pred' and [falses] is all the rest."""
455 for item
in iterable
:
463 if __name__
== '__main__':
465 numeric_options
, args
= partition(lambda s
: re
.match('-[0-9]+$', s
),
467 n_patches
= int(numeric_options
[-1][1:]) if numeric_options
else 0
469 optlist
, args
= getopt
.getopt(args
, 'bhlstf',
472 "skip-block-whitespace",
473 "skip-leading-whitespace",
474 "skip-signoff-lines",
475 "skip-trailing-whitespace"])
477 print("Unknown option encountered. Please rerun with -h for help.")
481 if o
in ("-h", "--help"):
484 elif o
in ("-b", "--skip-block-whitespace"):
485 skip_block_whitespace_check
= True
486 elif o
in ("-l", "--skip-leading-whitespace"):
487 skip_leading_whitespace_check
= True
488 elif o
in ("-s", "--skip-signoff-lines"):
489 skip_signoff_check
= True
490 elif o
in ("-t", "--skip-trailing-whitespace"):
491 skip_trailing_whitespace_check
= True
492 elif o
in ("-f", "--check-file"):
495 print("Unknown option '%s'" % o
)
498 if sys
.stdout
.isatty():
503 for i
in reversed(range(0, n_patches
)):
504 revision
= 'HEAD~%d' % i
505 f
= os
.popen('git format-patch -1 --stdout %s' % revision
, 'r')
509 print('== Checking %s ==' % revision
)
510 result
= ovs_checkpatch_parse(patch
, revision
)
511 ovs_checkpatch_print_result(result
)
519 if sys
.stdin
.isatty():
522 result
= ovs_checkpatch_parse(sys
.stdin
.read(), '-')
523 ovs_checkpatch_print_result(result
)
525 sys
.exit(ovs_checkpatch_file(filename
))