]> git.proxmox.com Git - mirror_ovs.git/blob - utilities/checkpatch.py
utilities: Add gdb debug commands to dump lists and pmd info
[mirror_ovs.git] / utilities / checkpatch.py
1 #!/usr/bin/env python
2 # Copyright (c) 2016, 2017 Red Hat, Inc.
3 # Copyright (c) 2018 Nicira, Inc.
4 #
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:
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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
17
18 import email
19 import getopt
20 import os
21 import re
22 import sys
23
24 try:
25 import enchant
26
27 extra_keywords = ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd', 'netdev',
28 'selinux', 'ovs-ctl', 'dpctl', 'ofctl', 'openvswitch',
29 'dpdk', 'hugepage', 'hugepages', 'pmd', 'upcall',
30 'vhost', 'rx', 'tx', 'vhostuser', 'openflow', 'qsort',
31 'rxq', 'txq', 'perf', 'stats', 'struct', 'int',
32 'char', 'bool', 'upcalls', 'nicira', 'bitmask', 'ipv4',
33 'ipv6', 'tcp', 'tcp4', 'tcpv4', 'udp', 'udp4', 'udpv4',
34 'icmp', 'icmp4', 'icmpv6', 'vlan', 'vxlan', 'cksum',
35 'csum', 'checksum', 'ofproto', 'numa', 'mempool',
36 'mempools', 'mbuf', 'mbufs', 'hmap', 'cmap', 'smap',
37 'dhcpv4', 'dhcp', 'dhcpv6', 'opts', 'metadata',
38 'geneve', 'mutex', 'netdev', 'netdevs', 'subtable',
39 'virtio', 'qos', 'policer', 'datapath', 'tunctl',
40 'attr', 'ethernet', 'ether', 'defrag', 'defragment',
41 'loopback', 'sflow', 'acl', 'initializer', 'recirc',
42 'xlated', 'unclosed', 'netlink', 'msec', 'usec',
43 'nsec', 'ms', 'us', 'ns', 'kilobits', 'kbps',
44 'kilobytes', 'megabytes', 'mbps', 'gigabytes', 'gbps',
45 'megabits', 'gigabits', 'pkts', 'tuple', 'miniflow',
46 'megaflow', 'conntrack', 'vlans', 'vxlans', 'arg',
47 'tpid', 'xbundle', 'xbundles', 'mbundle', 'mbundles',
48 'netflow', 'localnet', 'odp', 'pre', 'dst', 'dest',
49 'src', 'ethertype', 'cvlan', 'ips', 'msg', 'msgs',
50 'liveness', 'userspace', 'eventmask', 'datapaths',
51 'slowpath', 'fastpath', 'multicast', 'unicast',
52 'revalidation', 'namespace', 'qdisc', 'uuid', 'ofport',
53 'subnet', 'revalidation', 'revalidator', 'revalidate',
54 'l2', 'l3', 'l4', 'openssl', 'mtu', 'ifindex', 'enum',
55 'enums', 'http', 'https', 'num', 'vconn', 'vconns',
56 'conn', 'nat', 'memset', 'memcmp', 'strcmp',
57 'strcasecmp', 'tc', 'ufid', 'api', 'ofpbuf', 'ofpbufs',
58 'hashmaps', 'hashmap', 'deref', 'dereference', 'hw',
59 'prio', 'sendmmsg', 'sendmsg', 'malloc', 'free', 'alloc',
60 'pid', 'ppid', 'pgid', 'uid', 'gid', 'sid', 'utime',
61 'stime', 'cutime', 'cstime', 'vsize', 'rss', 'rsslim',
62 'whcan', 'gtime', 'eip', 'rip', 'cgtime', 'dbg', 'gw',
63 'sbrec', 'bfd', 'sizeof', 'pmds', 'nic', 'nics', 'hwol',
64 'encap', 'decap', 'tlv', 'tlvs', 'decapsulation', 'fd',
65 'cacheline', 'xlate', 'skiplist', 'idl', 'comparator',
66 'natting', 'alg', 'pasv', 'epasv', 'wildcard', 'nated',
67 'amd64', 'x86_64', 'recirculation']
68
69 spell_check_dict = enchant.Dict("en_US")
70 for kw in extra_keywords:
71 spell_check_dict.add(kw)
72
73 no_spellcheck = False
74 except:
75 no_spellcheck = True
76
77 __errors = 0
78 __warnings = 0
79 print_file_name = None
80 checking_file = False
81 total_line = 0
82 colors = False
83 spellcheck_comments = False
84
85
86 def get_color_end():
87 global colors
88 if colors:
89 return "\033[00m"
90 return ""
91
92
93 def get_red_begin():
94 global colors
95 if colors:
96 return "\033[91m"
97 return ""
98
99
100 def get_yellow_begin():
101 global colors
102 if colors:
103 return "\033[93m"
104 return ""
105
106
107 def print_error(message):
108 global __errors
109 print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message))
110
111 __errors = __errors + 1
112
113
114 def print_warning(message):
115 global __warnings
116 print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message))
117
118 __warnings = __warnings + 1
119
120
121 def reset_counters():
122 global __errors, __warnings, total_line
123
124 __errors = 0
125 __warnings = 0
126 total_line = 0
127
128
129 # These are keywords whose names are normally followed by a space and
130 # something in parentheses (usually an expression) then a left curly brace.
131 #
132 # 'do' almost qualifies but it's also used as "do { ... } while (...);".
133 __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*'
134
135 __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
136 __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
137 __regex_leading_with_whitespace_at_all = re.compile(r'^\s+')
138 __regex_leading_with_spaces = re.compile(r'^ +[\S]+')
139 __regex_trailing_whitespace = re.compile(r'[^\S]+$')
140 __regex_single_line_feed = re.compile(r'^\f$')
141 __regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]'
142 % __parenthesized_constructs)
143 __regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]'
144 % __parenthesized_constructs)
145 __regex_for_if_parens_whitespace = \
146 re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs)
147 __regex_is_for_if_single_line_bracket = \
148 re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs)
149 __regex_ends_with_bracket = \
150 re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
151 __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]')
152 __regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)')
153 __regex_has_comment = re.compile(r'.*(/\*|\*\s)')
154 __regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$')
155 __regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$')
156 __regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$')
157 __regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE)
158 __regex_added_doc_rst = re.compile(
159 r'\ndiff .*Documentation/.*rst\nnew file mode')
160
161 skip_leading_whitespace_check = False
162 skip_trailing_whitespace_check = False
163 skip_block_whitespace_check = False
164 skip_signoff_check = False
165
166 # Don't enforce character limit on files that include these characters in their
167 # name, as they may have legitimate reasons to have longer lines.
168 #
169 # Python isn't checked as flake8 performs these checks during build.
170 line_length_blacklist = re.compile(
171 r'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules')
172
173 # Don't enforce a requirement that leading whitespace be all spaces on
174 # files that include these characters in their name, since these kinds
175 # of files need lines with leading tabs.
176 leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules')
177
178
179 def is_subtracted_line(line):
180 """Returns TRUE if the line in question has been removed."""
181 return __regex_subtracted_line.search(line) is not None
182
183
184 def is_added_line(line):
185 """Returns TRUE if the line in question is an added line.
186 """
187 global checking_file
188 return __regex_added_line.search(line) is not None or checking_file
189
190
191 def added_line(line):
192 """Returns the line formatted properly by removing diff syntax"""
193 global checking_file
194 if not checking_file:
195 return line[1:]
196 return line
197
198
199 def leading_whitespace_is_spaces(line):
200 """Returns TRUE if the leading whitespace in added lines is spaces
201 """
202 if skip_leading_whitespace_check:
203 return True
204 if (__regex_leading_with_whitespace_at_all.search(line) is not None and
205 __regex_single_line_feed.search(line) is None):
206 return __regex_leading_with_spaces.search(line) is not None
207
208 return True
209
210
211 def trailing_whitespace_or_crlf(line):
212 """Returns TRUE if the trailing characters is whitespace
213 """
214 if skip_trailing_whitespace_check:
215 return False
216 return (__regex_trailing_whitespace.search(line) is not None and
217 __regex_single_line_feed.search(line) is None)
218
219
220 def if_and_for_whitespace_checks(line):
221 """Return TRUE if there is appropriate whitespace after if, for, while
222 """
223 if skip_block_whitespace_check:
224 return True
225 if (__regex_for_if_missing_whitespace.search(line) is not None or
226 __regex_for_if_too_much_whitespace.search(line) is not None or
227 __regex_for_if_parens_whitespace.search(line)):
228 return False
229 return True
230
231
232 def if_and_for_end_with_bracket_check(line):
233 """Return TRUE if there is not a bracket at the end of an if, for, while
234 block which fits on a single line ie: 'if (foo)'"""
235
236 def balanced_parens(line):
237 """This is a rather naive counter - it won't deal with quotes"""
238 balance = 0
239 for letter in line:
240 if letter == '(':
241 balance += 1
242 elif letter == ')':
243 balance -= 1
244 return balance == 0
245
246 if __regex_is_for_if_single_line_bracket.search(line) is not None:
247 if not balanced_parens(line):
248 return True
249 if __regex_ends_with_bracket.search(line) is None:
250 return False
251 if __regex_conditional_else_bracing.match(line) is not None:
252 return False
253 if __regex_conditional_else_bracing2.match(line) is not None:
254 return False
255 return True
256
257
258 def pointer_whitespace_check(line):
259 """Return TRUE if there is no space between a pointer name and the
260 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
261 return __regex_ptr_declaration_missing_whitespace.search(line) is not None
262
263
264 def line_length_check(line):
265 """Return TRUE if the line length is too long"""
266 if len(line) > 79:
267 print_warning("Line is %d characters long (recommended limit is 79)"
268 % len(line))
269 return True
270 return False
271
272
273 def is_comment_line(line):
274 """Returns TRUE if the current line is part of a block comment."""
275 return __regex_is_comment_line.match(line) is not None
276
277
278 def has_comment(line):
279 """Returns TRUE if the current line contains a comment or is part of
280 a block comment."""
281 return __regex_has_comment.match(line) is not None
282
283
284 def trailing_operator(line):
285 """Returns TRUE if the current line ends with an operatorsuch as ? or :"""
286 return __regex_trailing_operator.match(line) is not None
287
288
289 def has_xxx_mark(line):
290 """Returns TRUE if the current line contains 'xxx'."""
291 return __regex_has_xxx_mark.match(line) is not None
292
293
294 def filter_comments(current_line, keep=False):
295 """remove all of the c-style comments in a line"""
296 STATE_NORMAL = 0
297 STATE_COMMENT_SLASH = 1
298 STATE_COMMENT_CONTENTS = 3
299 STATE_COMMENT_END_SLASH = 4
300
301 state = STATE_NORMAL
302 sanitized_line = ''
303 check_state = STATE_NORMAL
304 only_whitespace = True
305
306 if keep:
307 check_state = STATE_COMMENT_CONTENTS
308
309 for c in current_line:
310 if c == '/':
311 if state == STATE_NORMAL:
312 state = STATE_COMMENT_SLASH
313 elif state == STATE_COMMENT_SLASH:
314 # This is for c++ style comments. We will warn later
315 return sanitized_line[:1]
316 elif state == STATE_COMMENT_END_SLASH:
317 c = ''
318 state = STATE_NORMAL
319 elif c == '*':
320 if only_whitespace:
321 # just assume this is a continuation from the previous line
322 # as a comment
323 state = STATE_COMMENT_END_SLASH
324 elif state == STATE_COMMENT_SLASH:
325 state = STATE_COMMENT_CONTENTS
326 sanitized_line = sanitized_line[:-1]
327 elif state == STATE_COMMENT_CONTENTS:
328 state = STATE_COMMENT_END_SLASH
329 elif state == STATE_COMMENT_END_SLASH:
330 # Need to re-introduce the star from the previous state, since
331 # it may have been clipped by the state check below.
332 c = '*' + c
333 state = STATE_COMMENT_CONTENTS
334 elif state == STATE_COMMENT_SLASH:
335 # Need to re-introduce the slash from the previous state, since
336 # it may have been clipped by the state check below.
337 c = '/' + c
338 state = STATE_NORMAL
339
340 if state != check_state:
341 c = ''
342
343 if not c.isspace():
344 only_whitespace = False
345
346 sanitized_line += c
347
348 return sanitized_line
349
350
351 def check_comment_spelling(line):
352 if no_spellcheck or not spellcheck_comments:
353 return False
354
355 comment_words = filter_comments(line, True).replace(':', ' ').split(' ')
356 for word in comment_words:
357 skip = False
358 strword = re.subn(r'\W+', '', word)[0].replace(',', '')
359 if len(strword) and not spell_check_dict.check(strword.lower()):
360 if any([check_char in word
361 for check_char in ['=', '(', '-', '_', '/', '\'']]):
362 skip = True
363
364 # special case the '.'
365 if '.' in word and not word.endswith('.'):
366 skip = True
367
368 # skip proper nouns and references to macros
369 if strword.isupper() or (strword[0].isupper() and
370 strword[1:].islower()):
371 skip = True
372
373 # skip words that start with numbers
374 if strword.startswith(tuple('0123456789')):
375 skip = True
376
377 if not skip:
378 print_warning("Check for spelling mistakes (e.g. \"%s\")"
379 % strword)
380 return True
381
382 return False
383
384
385 def __check_doc_is_listed(text, doctype, docdir, docfile):
386 if doctype == 'rst':
387 beginre = re.compile(r'\+\+\+.*{}/index.rst'.format(docdir))
388 docre = re.compile(r'\n\+.*{}'.format(docfile.replace('.rst', '')))
389 elif doctype == 'automake':
390 beginre = re.compile(r'\+\+\+.*Documentation/automake.mk')
391 docre = re.compile(r'\n\+\t{}/{}'.format(docdir, docfile))
392 else:
393 raise NotImplementedError("Invalid doctype: {}".format(doctype))
394
395 res = beginre.search(text)
396 if res is None:
397 return True
398
399 hunkstart = res.span()[1]
400 hunkre = re.compile(r'\n(---|\+\+\+) (\S+)')
401 res = hunkre.search(text[hunkstart:])
402 if res is None:
403 hunkend = len(text)
404 else:
405 hunkend = hunkstart + res.span()[0]
406
407 hunk = text[hunkstart:hunkend]
408 # find if the file is being added.
409 if docre.search(hunk) is not None:
410 return False
411
412 return True
413
414
415 def __check_new_docs(text, doctype):
416 """Check if the documentation is listed properly. If doctype is 'rst' then
417 the index.rst is checked. If the doctype is 'automake' then automake.mk
418 is checked. Returns TRUE if the new file is not listed."""
419 failed = False
420 new_docs = __regex_added_doc_rst.findall(text)
421 for doc in new_docs:
422 docpathname = doc.split(' ')[2]
423 gitdocdir, docfile = os.path.split(docpathname.rstrip('\n'))
424 if docfile == "index.rst":
425 continue
426
427 if gitdocdir.startswith('a/'):
428 docdir = gitdocdir.replace('a/', '', 1)
429 else:
430 docdir = gitdocdir
431
432 if __check_doc_is_listed(text, doctype, docdir, docfile):
433 if doctype == 'rst':
434 print_warning("New doc {} not listed in {}/index.rst".format(
435 docfile, docdir))
436 elif doctype == 'automake':
437 print_warning("New doc {} not listed in "
438 "Documentation/automake.mk".format(docfile))
439 else:
440 raise NotImplementedError("Invalid doctype: {}".format(
441 doctype))
442
443 failed = True
444
445 return failed
446
447
448 def check_doc_docs_automake(text):
449 return __check_new_docs(text, 'automake')
450
451
452 def check_new_docs_index(text):
453 return __check_new_docs(text, 'rst')
454
455
456 file_checks = [
457 {'regex': __regex_added_doc_rst,
458 'check': check_new_docs_index},
459 {'regex': __regex_added_doc_rst,
460 'check': check_doc_docs_automake}
461 ]
462
463 checks = [
464 {'regex': None,
465 'match_name': lambda x: not line_length_blacklist.search(x),
466 'check': lambda x: line_length_check(x)},
467
468 {'regex': None,
469 'match_name': lambda x: not leading_whitespace_blacklist.search(x),
470 'check': lambda x: not leading_whitespace_is_spaces(x),
471 'print': lambda: print_warning("Line has non-spaces leading whitespace")},
472
473 {'regex': None, 'match_name': None,
474 'check': lambda x: trailing_whitespace_or_crlf(x),
475 'print': lambda: print_warning("Line has trailing whitespace")},
476
477 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
478 'prereq': lambda x: not is_comment_line(x),
479 'check': lambda x: not if_and_for_whitespace_checks(x),
480 'print': lambda: print_error("Improper whitespace around control block")},
481
482 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
483 'prereq': lambda x: not is_comment_line(x),
484 'check': lambda x: not if_and_for_end_with_bracket_check(x),
485 'print': lambda: print_error("Inappropriate bracing around statement")},
486
487 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
488 'prereq': lambda x: not is_comment_line(x),
489 'check': lambda x: pointer_whitespace_check(x),
490 'print':
491 lambda: print_error("Inappropriate spacing in pointer declaration")},
492
493 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
494 'prereq': lambda x: not is_comment_line(x),
495 'check': lambda x: trailing_operator(x),
496 'print':
497 lambda: print_error("Line has '?' or ':' operator at end of line")},
498
499 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
500 'prereq': lambda x: has_comment(x),
501 'check': lambda x: has_xxx_mark(x),
502 'print': lambda: print_warning("Comment with 'xxx' marker")},
503
504 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
505 'prereq': lambda x: has_comment(x),
506 'check': lambda x: check_comment_spelling(x)},
507 ]
508
509
510 def regex_function_factory(func_name):
511 regex = re.compile(r'\b%s\([^)]*\)' % func_name)
512 return lambda x: regex.search(x) is not None
513
514
515 def regex_error_factory(description):
516 return lambda: print_error(description)
517
518
519 std_functions = [
520 ('malloc', 'Use xmalloc() in place of malloc()'),
521 ('calloc', 'Use xcalloc() in place of calloc()'),
522 ('realloc', 'Use xrealloc() in place of realloc()'),
523 ('strdup', 'Use xstrdup() in place of strdup()'),
524 ('asprintf', 'Use xasprintf() in place of asprintf()'),
525 ('vasprintf', 'Use xvasprintf() in place of vasprintf()'),
526 ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'),
527 ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'),
528 ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'),
529 ('strerror', 'Use ovs_strerror() in place of strerror()'),
530 ('sleep', 'Use xsleep() in place of sleep()'),
531 ('abort', 'Use ovs_abort() in place of abort()'),
532 ('assert', 'Use ovs_assert() in place of assert()'),
533 ('error', 'Use ovs_error() in place of error()'),
534 ]
535 checks += [
536 {'regex': '(\.c|\.h)(\.in)?$',
537 'match_name': None,
538 'prereq': lambda x: not is_comment_line(x),
539 'check': regex_function_factory(function_name),
540 'print': regex_error_factory(description)}
541 for (function_name, description) in std_functions]
542
543
544 def regex_operator_factory(operator):
545 regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator)
546 return lambda x: regex.search(filter_comments(x)) is not None
547
548
549 infix_operators = \
550 [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=',
551 '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=',
552 '&=', '^=', '|=', '<<=', '>>=']] \
553 + ['[^<" ]<[^=" ]', '[^->" ]>[^=" ]', '[^ !()/"]\*[^/]', '[^ !&()"]&',
554 '[^" +(]\+[^"+;]', '[^" -(]-[^"->;]', '[^" <>=!^|+\-*/%&]=[^"=]',
555 '[^* ]/[^* ]']
556 checks += [
557 {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None,
558 'prereq': lambda x: not is_comment_line(x),
559 'check': regex_operator_factory(operator),
560 'print': lambda: print_warning("Line lacks whitespace around operator")}
561 for operator in infix_operators]
562
563
564 def get_file_type_checks(filename):
565 """Returns the list of checks for a file based on matching the filename
566 against regex."""
567 global checks
568 checkList = []
569 for check in checks:
570 if check['regex'] is None and check['match_name'] is None:
571 checkList.append(check)
572 if check['regex'] is not None and \
573 re.compile(check['regex']).search(filename) is not None:
574 checkList.append(check)
575 elif check['match_name'] is not None and check['match_name'](filename):
576 checkList.append(check)
577 return checkList
578
579
580 def run_checks(current_file, line, lineno):
581 """Runs the various checks for the particular line. This will take
582 filename into account."""
583 global checking_file, total_line
584 print_line = False
585 for check in get_file_type_checks(current_file):
586 if 'prereq' in check and not check['prereq'](line):
587 continue
588 if check['check'](line):
589 if 'print' in check:
590 check['print']()
591 print_line = True
592
593 if print_line:
594 if checking_file:
595 print("%s:%d:" % (current_file, lineno))
596 else:
597 print("#%d FILE: %s:%d:" % (total_line, current_file, lineno))
598 print("%s\n" % line)
599
600
601 def run_file_checks(text):
602 """Runs the various checks for the text."""
603 for check in file_checks:
604 if check['regex'].search(text) is not None:
605 check['check'](text)
606
607
608 def ovs_checkpatch_parse(text, filename):
609 global print_file_name, total_line, checking_file
610
611 PARSE_STATE_HEADING = 0
612 PARSE_STATE_DIFF_HEADER = 1
613 PARSE_STATE_CHANGE_BODY = 2
614
615 lineno = 0
616 signatures = []
617 co_authors = []
618 parse = 0
619 current_file = filename if checking_file else ''
620 previous_file = ''
621 scissors = re.compile(r'^[\w]*---[\w]*')
622 hunks = re.compile('^(---|\+\+\+) (\S+)')
623 hunk_differences = re.compile(
624 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
625 is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$',
626 re.I | re.M | re.S)
627 is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$',
628 re.I | re.M | re.S)
629 is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$',
630 re.I | re.M | re.S)
631
632 reset_counters()
633
634 for line in text.split('\n'):
635 if current_file != previous_file:
636 previous_file = current_file
637
638 lineno = lineno + 1
639 total_line = total_line + 1
640 if len(line) <= 0:
641 continue
642
643 if checking_file:
644 parse = PARSE_STATE_CHANGE_BODY
645
646 if parse == PARSE_STATE_DIFF_HEADER:
647 match = hunks.match(line)
648 if match:
649 parse = PARSE_STATE_CHANGE_BODY
650 current_file = match.group(2)[2:]
651 print_file_name = current_file
652 continue
653 elif parse == PARSE_STATE_HEADING:
654 if scissors.match(line):
655 parse = PARSE_STATE_DIFF_HEADER
656 if not skip_signoff_check:
657 if len(signatures) == 0:
658 print_error("No signatures found.")
659 elif len(signatures) != 1 + len(co_authors):
660 print_error("Too many signoffs; "
661 "are you missing Co-authored-by lines?")
662 if not set(co_authors) <= set(signatures):
663 print_error("Co-authored-by/Signed-off-by corruption")
664 elif is_signature.match(line):
665 m = is_signature.match(line)
666 signatures.append(m.group(3))
667 elif is_co_author.match(line):
668 m = is_co_author.match(line)
669 co_authors.append(m.group(3))
670 elif is_gerrit_change_id.match(line):
671 print_error(
672 "Remove Gerrit Change-Id's before submitting upstream.")
673 print("%d: %s\n" % (lineno, line))
674 elif parse == PARSE_STATE_CHANGE_BODY:
675 newfile = hunks.match(line)
676 if newfile:
677 current_file = newfile.group(2)[2:]
678 print_file_name = current_file
679 continue
680 reset_line_number = hunk_differences.match(line)
681 if reset_line_number:
682 lineno = int(reset_line_number.group(3))
683 if lineno < 0:
684 lineno = -1 * lineno
685 lineno -= 1
686 if is_subtracted_line(line):
687 lineno -= 1
688 if not is_added_line(line):
689 continue
690
691 cmp_line = added_line(line)
692
693 # Skip files which have /datapath in them, since they are
694 # linux or windows coding standards
695 if current_file.startswith('datapath'):
696 continue
697 if current_file.startswith('include/linux'):
698 continue
699 run_checks(current_file, cmp_line, lineno)
700
701 run_file_checks(text)
702 if __errors or __warnings:
703 return -1
704 return 0
705
706
707 def usage():
708 print("""\
709 Open vSwitch checkpatch.py
710 Checks a patch for trivial mistakes.
711 usage:
712 %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...]
713
714 Input options:
715 -f|--check-file Arguments are source files, not patches.
716 -1, -2, ... Check recent commits in this repo.
717
718 Check options:
719 -h|--help This help message
720 -b|--skip-block-whitespace Skips the if/while/for whitespace tests
721 -l|--skip-leading-whitespace Skips the leading whitespace test
722 -s|--skip-signoff-lines Tolerate missing Signed-off-by line
723 -S|--spellcheck-comments Check C comments for possible spelling mistakes
724 -t|--skip-trailing-whitespace Skips the trailing whitespace test"""
725 % sys.argv[0])
726
727
728 def ovs_checkpatch_print_result(result):
729 global __warnings, __errors, total_line
730 if result < 0:
731 print("Lines checked: %d, Warnings: %d, Errors: %d\n" %
732 (total_line, __warnings, __errors))
733 else:
734 print("Lines checked: %d, no obvious problems found\n" % (total_line))
735
736
737 def ovs_checkpatch_file(filename):
738 try:
739 mail = email.message_from_file(open(filename, 'r'))
740 except:
741 print_error("Unable to parse file '%s'. Is it a patch?" % filename)
742 return -1
743
744 for part in mail.walk():
745 if part.get_content_maintype() == 'multipart':
746 continue
747 result = ovs_checkpatch_parse(part.get_payload(decode=False), filename)
748 ovs_checkpatch_print_result(result)
749 return result
750
751
752 def partition(pred, iterable):
753 """Returns [[trues], [falses]], where [trues] is the items in
754 'iterable' that satisfy 'pred' and [falses] is all the rest."""
755 trues = []
756 falses = []
757 for item in iterable:
758 if pred(item):
759 trues.append(item)
760 else:
761 falses.append(item)
762 return trues, falses
763
764
765 if __name__ == '__main__':
766 try:
767 numeric_options, args = partition(lambda s: re.match('-[0-9]+$', s),
768 sys.argv[1:])
769 n_patches = int(numeric_options[-1][1:]) if numeric_options else 0
770
771 optlist, args = getopt.getopt(args, 'bhlstfS',
772 ["check-file",
773 "help",
774 "skip-block-whitespace",
775 "skip-leading-whitespace",
776 "skip-signoff-lines",
777 "skip-trailing-whitespace",
778 "spellcheck-comments"])
779 except:
780 print("Unknown option encountered. Please rerun with -h for help.")
781 sys.exit(-1)
782
783 for o, a in optlist:
784 if o in ("-h", "--help"):
785 usage()
786 sys.exit(0)
787 elif o in ("-b", "--skip-block-whitespace"):
788 skip_block_whitespace_check = True
789 elif o in ("-l", "--skip-leading-whitespace"):
790 skip_leading_whitespace_check = True
791 elif o in ("-s", "--skip-signoff-lines"):
792 skip_signoff_check = True
793 elif o in ("-t", "--skip-trailing-whitespace"):
794 skip_trailing_whitespace_check = True
795 elif o in ("-f", "--check-file"):
796 checking_file = True
797 elif o in ("-S", "--spellcheck-comments"):
798 if no_spellcheck:
799 print("WARNING: The enchant library isn't availble.")
800 print(" Please install python enchant.")
801 else:
802 spellcheck_comments = True
803 else:
804 print("Unknown option '%s'" % o)
805 sys.exit(-1)
806
807 if sys.stdout.isatty():
808 colors = True
809
810 if n_patches:
811 status = 0
812
813 git_log = 'git log --no-color --no-merges --pretty=format:"%H %s" '
814 with os.popen(git_log + '-%d' % n_patches, 'r') as f:
815 commits = f.read().split("\n")
816
817 for i in reversed(range(0, n_patches)):
818 revision, name = commits[i].split(" ", 1)
819 f = os.popen('git format-patch -1 --stdout %s' % revision, 'r')
820 patch = f.read()
821 f.close()
822
823 print('== Checking %s ("%s") ==' % (revision[0:12], name))
824 result = ovs_checkpatch_parse(patch, revision)
825 ovs_checkpatch_print_result(result)
826 if result:
827 status = -1
828 sys.exit(status)
829
830 if not args:
831 if sys.stdin.isatty():
832 usage()
833 sys.exit(-1)
834 result = ovs_checkpatch_parse(sys.stdin.read(), '-')
835 ovs_checkpatch_print_result(result)
836 sys.exit(result)
837
838 status = 0
839 for filename in args:
840 print('== Checking "%s" ==' % filename)
841 result = ovs_checkpatch_file(filename)
842 if result:
843 status = -1
844 sys.exit(status)