]>
Commit | Line | Data |
---|---|---|
c599d5cc | 1 | #!/usr/bin/env python |
ebba2af6 | 2 | # Copyright (c) 2016, 2017 Red Hat, Inc. |
4e99b70d | 3 | # Copyright (c) 2018 Nicira, Inc. |
c599d5cc AC |
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 | |
a1fccabc | 20 | import os |
c599d5cc AC |
21 | import re |
22 | import sys | |
23 | ||
999c7773 AC |
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 | ||
a9e5ac0f BS |
77 | RETURN_CHECK_INITIAL_STATE = 0 |
78 | RETURN_CHECK_STATE_WITH_RETURN = 1 | |
79 | RETURN_CHECK_AWAITING_BRACE = 2 | |
c599d5cc AC |
80 | __errors = 0 |
81 | __warnings = 0 | |
a9e5ac0f | 82 | empty_return_check_state = 0 |
3239c793 | 83 | print_file_name = None |
fb9410d8 | 84 | checking_file = False |
ebba2af6 AC |
85 | total_line = 0 |
86 | colors = False | |
999c7773 | 87 | spellcheck_comments = False |
de8fa82a | 88 | quiet = False |
3239c793 AC |
89 | |
90 | ||
ebba2af6 AC |
91 | def get_color_end(): |
92 | global colors | |
93 | if colors: | |
94 | return "\033[00m" | |
95 | return "" | |
c599d5cc AC |
96 | |
97 | ||
ebba2af6 AC |
98 | def get_red_begin(): |
99 | global colors | |
100 | if colors: | |
101 | return "\033[91m" | |
102 | return "" | |
103 | ||
104 | ||
105 | def get_yellow_begin(): | |
106 | global colors | |
107 | if colors: | |
108 | return "\033[93m" | |
109 | return "" | |
110 | ||
111 | ||
112 | def print_error(message): | |
c599d5cc | 113 | global __errors |
ebba2af6 | 114 | print("%sERROR%s: %s" % (get_red_begin(), get_color_end(), message)) |
c599d5cc AC |
115 | |
116 | __errors = __errors + 1 | |
117 | ||
118 | ||
ebba2af6 | 119 | def print_warning(message): |
c599d5cc | 120 | global __warnings |
ebba2af6 | 121 | print("%sWARNING%s: %s" % (get_yellow_begin(), get_color_end(), message)) |
c599d5cc AC |
122 | |
123 | __warnings = __warnings + 1 | |
124 | ||
125 | ||
81c2316f | 126 | def reset_counters(): |
f61d40de | 127 | global __errors, __warnings, total_line |
81c2316f IM |
128 | |
129 | __errors = 0 | |
130 | __warnings = 0 | |
f61d40de | 131 | total_line = 0 |
81c2316f IM |
132 | |
133 | ||
b537de13 BP |
134 | # These are keywords whose names are normally followed by a space and |
135 | # something in parentheses (usually an expression) then a left curly brace. | |
136 | # | |
137 | # 'do' almost qualifies but it's also used as "do { ... } while (...);". | |
138 | __parenthesized_constructs = 'if|for|while|switch|[_A-Z]+FOR_EACH[_A-Z]*' | |
139 | ||
c599d5cc | 140 | __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*') |
4d7f5e51 | 141 | __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*') |
c599d5cc AC |
142 | __regex_leading_with_whitespace_at_all = re.compile(r'^\s+') |
143 | __regex_leading_with_spaces = re.compile(r'^ +[\S]+') | |
144 | __regex_trailing_whitespace = re.compile(r'[^\S]+$') | |
c61e93d6 | 145 | __regex_single_line_feed = re.compile(r'^\f$') |
b537de13 BP |
146 | __regex_for_if_missing_whitespace = re.compile(r' +(%s)[\(]' |
147 | % __parenthesized_constructs) | |
148 | __regex_for_if_too_much_whitespace = re.compile(r' +(%s) +[\(]' | |
149 | % __parenthesized_constructs) | |
a1193b4d | 150 | __regex_for_if_parens_whitespace = \ |
b537de13 | 151 | re.compile(r' +(%s) \( +[\s\S]+\)' % __parenthesized_constructs) |
30c7ffd5 | 152 | __regex_is_for_if_single_line_bracket = \ |
b537de13 | 153 | re.compile(r'^ +(%s) \(.*\)' % __parenthesized_constructs) |
a1193b4d AC |
154 | __regex_ends_with_bracket = \ |
155 | re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$') | |
d56ec3bc | 156 | __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*[^*]') |
d6ec6c10 | 157 | __regex_is_comment_line = re.compile(r'^\s*(/\*|\*\s)') |
4e99b70d | 158 | __regex_has_comment = re.compile(r'.*(/\*|\*\s)') |
12f62e9d | 159 | __regex_trailing_operator = re.compile(r'^[^ ]* [^ ]*[?:]$') |
3f9e248f JS |
160 | __regex_conditional_else_bracing = re.compile(r'^\s*else\s*{?$') |
161 | __regex_conditional_else_bracing2 = re.compile(r'^\s*}\selse\s*$') | |
4e99b70d | 162 | __regex_has_xxx_mark = re.compile(r'.*xxx.*', re.IGNORECASE) |
8503a516 FL |
163 | __regex_added_doc_rst = re.compile( |
164 | r'\ndiff .*Documentation/.*rst\nnew file mode') | |
a9e5ac0f | 165 | __regex_empty_return = re.compile(r'\s*return;') |
16770c6d BS |
166 | __regex_if_macros = re.compile(r'^ +(%s) \([\S][\s\S]+[\S]\) { \\' % |
167 | __parenthesized_constructs) | |
c599d5cc AC |
168 | |
169 | skip_leading_whitespace_check = False | |
170 | skip_trailing_whitespace_check = False | |
171 | skip_block_whitespace_check = False | |
172 | skip_signoff_check = False | |
173 | ||
6982ee96 JS |
174 | # Don't enforce character limit on files that include these characters in their |
175 | # name, as they may have legitimate reasons to have longer lines. | |
176 | # | |
177 | # Python isn't checked as flake8 performs these checks during build. | |
9aef43f0 BP |
178 | line_length_blacklist = re.compile( |
179 | r'\.(am|at|etc|in|m4|mk|patch|py)$|debian/rules') | |
6982ee96 | 180 | |
ae9c0985 BP |
181 | # Don't enforce a requirement that leading whitespace be all spaces on |
182 | # files that include these characters in their name, since these kinds | |
183 | # of files need lines with leading tabs. | |
9aef43f0 | 184 | leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules') |
ae9c0985 | 185 | |
c599d5cc | 186 | |
4d7f5e51 AC |
187 | def is_subtracted_line(line): |
188 | """Returns TRUE if the line in question has been removed.""" | |
189 | return __regex_subtracted_line.search(line) is not None | |
190 | ||
fb9410d8 | 191 | |
c599d5cc AC |
192 | def is_added_line(line): |
193 | """Returns TRUE if the line in question is an added line. | |
194 | """ | |
fb9410d8 AC |
195 | global checking_file |
196 | return __regex_added_line.search(line) is not None or checking_file | |
197 | ||
198 | ||
199 | def added_line(line): | |
200 | """Returns the line formatted properly by removing diff syntax""" | |
201 | global checking_file | |
202 | if not checking_file: | |
203 | return line[1:] | |
204 | return line | |
c599d5cc AC |
205 | |
206 | ||
207 | def leading_whitespace_is_spaces(line): | |
208 | """Returns TRUE if the leading whitespace in added lines is spaces | |
209 | """ | |
210 | if skip_leading_whitespace_check: | |
211 | return True | |
c61e93d6 DDP |
212 | if (__regex_leading_with_whitespace_at_all.search(line) is not None and |
213 | __regex_single_line_feed.search(line) is None): | |
c599d5cc | 214 | return __regex_leading_with_spaces.search(line) is not None |
c61e93d6 | 215 | |
c599d5cc AC |
216 | return True |
217 | ||
218 | ||
219 | def trailing_whitespace_or_crlf(line): | |
220 | """Returns TRUE if the trailing characters is whitespace | |
221 | """ | |
222 | if skip_trailing_whitespace_check: | |
223 | return False | |
c61e93d6 DDP |
224 | return (__regex_trailing_whitespace.search(line) is not None and |
225 | __regex_single_line_feed.search(line) is None) | |
c599d5cc AC |
226 | |
227 | ||
228 | def if_and_for_whitespace_checks(line): | |
229 | """Return TRUE if there is appropriate whitespace after if, for, while | |
230 | """ | |
231 | if skip_block_whitespace_check: | |
232 | return True | |
233 | if (__regex_for_if_missing_whitespace.search(line) is not None or | |
234 | __regex_for_if_too_much_whitespace.search(line) is not None or | |
235 | __regex_for_if_parens_whitespace.search(line)): | |
236 | return False | |
237 | return True | |
238 | ||
239 | ||
30c7ffd5 AC |
240 | def if_and_for_end_with_bracket_check(line): |
241 | """Return TRUE if there is not a bracket at the end of an if, for, while | |
242 | block which fits on a single line ie: 'if (foo)'""" | |
243 | ||
244 | def balanced_parens(line): | |
245 | """This is a rather naive counter - it won't deal with quotes""" | |
246 | balance = 0 | |
247 | for letter in line: | |
973496b9 | 248 | if letter == '(': |
30c7ffd5 | 249 | balance += 1 |
973496b9 | 250 | elif letter == ')': |
30c7ffd5 | 251 | balance -= 1 |
973496b9 | 252 | return balance == 0 |
30c7ffd5 AC |
253 | |
254 | if __regex_is_for_if_single_line_bracket.search(line) is not None: | |
255 | if not balanced_parens(line): | |
256 | return True | |
16770c6d BS |
257 | |
258 | if __regex_ends_with_bracket.search(line) is None and \ | |
259 | __regex_if_macros.match(line) is None: | |
30c7ffd5 | 260 | return False |
3f9e248f JS |
261 | if __regex_conditional_else_bracing.match(line) is not None: |
262 | return False | |
263 | if __regex_conditional_else_bracing2.match(line) is not None: | |
264 | return False | |
30c7ffd5 AC |
265 | return True |
266 | ||
267 | ||
6fc2799e JS |
268 | def pointer_whitespace_check(line): |
269 | """Return TRUE if there is no space between a pointer name and the | |
270 | asterisk that denotes this is a apionter type, ie: 'struct foo*'""" | |
271 | return __regex_ptr_declaration_missing_whitespace.search(line) is not None | |
272 | ||
273 | ||
517f04ad AC |
274 | def line_length_check(line): |
275 | """Return TRUE if the line length is too long""" | |
276 | if len(line) > 79: | |
860ffe70 BP |
277 | print_warning("Line is %d characters long (recommended limit is 79)" |
278 | % len(line)) | |
517f04ad AC |
279 | return True |
280 | return False | |
281 | ||
282 | ||
d6ec6c10 BP |
283 | def is_comment_line(line): |
284 | """Returns TRUE if the current line is part of a block comment.""" | |
285 | return __regex_is_comment_line.match(line) is not None | |
286 | ||
8df9a0c4 | 287 | |
4e99b70d JP |
288 | def has_comment(line): |
289 | """Returns TRUE if the current line contains a comment or is part of | |
290 | a block comment.""" | |
291 | return __regex_has_comment.match(line) is not None | |
d6ec6c10 | 292 | |
8df9a0c4 | 293 | |
12f62e9d JS |
294 | def trailing_operator(line): |
295 | """Returns TRUE if the current line ends with an operatorsuch as ? or :""" | |
296 | return __regex_trailing_operator.match(line) is not None | |
297 | ||
8df9a0c4 | 298 | |
4e99b70d JP |
299 | def has_xxx_mark(line): |
300 | """Returns TRUE if the current line contains 'xxx'.""" | |
301 | return __regex_has_xxx_mark.match(line) is not None | |
302 | ||
12f62e9d | 303 | |
999c7773 | 304 | def filter_comments(current_line, keep=False): |
8361e647 AC |
305 | """remove all of the c-style comments in a line""" |
306 | STATE_NORMAL = 0 | |
307 | STATE_COMMENT_SLASH = 1 | |
308 | STATE_COMMENT_CONTENTS = 3 | |
309 | STATE_COMMENT_END_SLASH = 4 | |
310 | ||
311 | state = STATE_NORMAL | |
312 | sanitized_line = '' | |
313 | check_state = STATE_NORMAL | |
314 | only_whitespace = True | |
315 | ||
999c7773 AC |
316 | if keep: |
317 | check_state = STATE_COMMENT_CONTENTS | |
318 | ||
8361e647 AC |
319 | for c in current_line: |
320 | if c == '/': | |
321 | if state == STATE_NORMAL: | |
322 | state = STATE_COMMENT_SLASH | |
323 | elif state == STATE_COMMENT_SLASH: | |
324 | # This is for c++ style comments. We will warn later | |
325 | return sanitized_line[:1] | |
326 | elif state == STATE_COMMENT_END_SLASH: | |
327 | c = '' | |
328 | state = STATE_NORMAL | |
329 | elif c == '*': | |
330 | if only_whitespace: | |
331 | # just assume this is a continuation from the previous line | |
332 | # as a comment | |
333 | state = STATE_COMMENT_END_SLASH | |
334 | elif state == STATE_COMMENT_SLASH: | |
335 | state = STATE_COMMENT_CONTENTS | |
336 | sanitized_line = sanitized_line[:-1] | |
337 | elif state == STATE_COMMENT_CONTENTS: | |
338 | state = STATE_COMMENT_END_SLASH | |
339 | elif state == STATE_COMMENT_END_SLASH: | |
340 | # Need to re-introduce the star from the previous state, since | |
341 | # it may have been clipped by the state check below. | |
342 | c = '*' + c | |
343 | state = STATE_COMMENT_CONTENTS | |
344 | elif state == STATE_COMMENT_SLASH: | |
345 | # Need to re-introduce the slash from the previous state, since | |
346 | # it may have been clipped by the state check below. | |
347 | c = '/' + c | |
348 | state = STATE_NORMAL | |
349 | ||
350 | if state != check_state: | |
351 | c = '' | |
352 | ||
353 | if not c.isspace(): | |
354 | only_whitespace = False | |
355 | ||
356 | sanitized_line += c | |
357 | ||
358 | return sanitized_line | |
359 | ||
360 | ||
999c7773 AC |
361 | def check_comment_spelling(line): |
362 | if no_spellcheck or not spellcheck_comments: | |
363 | return False | |
364 | ||
365 | comment_words = filter_comments(line, True).replace(':', ' ').split(' ') | |
366 | for word in comment_words: | |
367 | skip = False | |
368 | strword = re.subn(r'\W+', '', word)[0].replace(',', '') | |
369 | if len(strword) and not spell_check_dict.check(strword.lower()): | |
370 | if any([check_char in word | |
371 | for check_char in ['=', '(', '-', '_', '/', '\'']]): | |
372 | skip = True | |
373 | ||
374 | # special case the '.' | |
375 | if '.' in word and not word.endswith('.'): | |
376 | skip = True | |
377 | ||
378 | # skip proper nouns and references to macros | |
379 | if strword.isupper() or (strword[0].isupper() and | |
380 | strword[1:].islower()): | |
381 | skip = True | |
382 | ||
383 | # skip words that start with numbers | |
384 | if strword.startswith(tuple('0123456789')): | |
385 | skip = True | |
386 | ||
387 | if not skip: | |
860ffe70 BP |
388 | print_warning("Check for spelling mistakes (e.g. \"%s\")" |
389 | % strword) | |
999c7773 AC |
390 | return True |
391 | ||
392 | return False | |
393 | ||
394 | ||
8503a516 FL |
395 | def __check_doc_is_listed(text, doctype, docdir, docfile): |
396 | if doctype == 'rst': | |
397 | beginre = re.compile(r'\+\+\+.*{}/index.rst'.format(docdir)) | |
398 | docre = re.compile(r'\n\+.*{}'.format(docfile.replace('.rst', ''))) | |
399 | elif doctype == 'automake': | |
400 | beginre = re.compile(r'\+\+\+.*Documentation/automake.mk') | |
401 | docre = re.compile(r'\n\+\t{}/{}'.format(docdir, docfile)) | |
402 | else: | |
403 | raise NotImplementedError("Invalid doctype: {}".format(doctype)) | |
404 | ||
405 | res = beginre.search(text) | |
406 | if res is None: | |
407 | return True | |
408 | ||
409 | hunkstart = res.span()[1] | |
410 | hunkre = re.compile(r'\n(---|\+\+\+) (\S+)') | |
411 | res = hunkre.search(text[hunkstart:]) | |
412 | if res is None: | |
413 | hunkend = len(text) | |
414 | else: | |
415 | hunkend = hunkstart + res.span()[0] | |
416 | ||
417 | hunk = text[hunkstart:hunkend] | |
418 | # find if the file is being added. | |
419 | if docre.search(hunk) is not None: | |
420 | return False | |
421 | ||
422 | return True | |
423 | ||
424 | ||
425 | def __check_new_docs(text, doctype): | |
426 | """Check if the documentation is listed properly. If doctype is 'rst' then | |
427 | the index.rst is checked. If the doctype is 'automake' then automake.mk | |
428 | is checked. Returns TRUE if the new file is not listed.""" | |
429 | failed = False | |
430 | new_docs = __regex_added_doc_rst.findall(text) | |
431 | for doc in new_docs: | |
432 | docpathname = doc.split(' ')[2] | |
433 | gitdocdir, docfile = os.path.split(docpathname.rstrip('\n')) | |
434 | if docfile == "index.rst": | |
435 | continue | |
436 | ||
437 | if gitdocdir.startswith('a/'): | |
438 | docdir = gitdocdir.replace('a/', '', 1) | |
439 | else: | |
440 | docdir = gitdocdir | |
441 | ||
442 | if __check_doc_is_listed(text, doctype, docdir, docfile): | |
443 | if doctype == 'rst': | |
444 | print_warning("New doc {} not listed in {}/index.rst".format( | |
445 | docfile, docdir)) | |
446 | elif doctype == 'automake': | |
447 | print_warning("New doc {} not listed in " | |
448 | "Documentation/automake.mk".format(docfile)) | |
449 | else: | |
450 | raise NotImplementedError("Invalid doctype: {}".format( | |
451 | doctype)) | |
452 | ||
453 | failed = True | |
454 | ||
455 | return failed | |
456 | ||
457 | ||
458 | def check_doc_docs_automake(text): | |
459 | return __check_new_docs(text, 'automake') | |
460 | ||
461 | ||
462 | def check_new_docs_index(text): | |
463 | return __check_new_docs(text, 'rst') | |
464 | ||
465 | ||
a9e5ac0f BS |
466 | def empty_return_with_brace(line): |
467 | """Returns TRUE if a function contains a return; followed | |
468 | by one or more line feeds and terminates with a '}' | |
469 | at start of line""" | |
470 | ||
471 | def empty_return(line): | |
472 | """Returns TRUE if a function has a 'return;'""" | |
473 | return __regex_empty_return.match(line) is not None | |
474 | ||
475 | global empty_return_check_state | |
476 | if empty_return_check_state == RETURN_CHECK_INITIAL_STATE \ | |
477 | and empty_return(line): | |
478 | empty_return_check_state = RETURN_CHECK_STATE_WITH_RETURN | |
479 | elif empty_return_check_state == RETURN_CHECK_STATE_WITH_RETURN \ | |
480 | and (re.match(r'^}$', line) or len(line) == 0): | |
481 | if re.match('^}$', line): | |
482 | empty_return_check_state = RETURN_CHECK_AWAITING_BRACE | |
483 | else: | |
484 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE | |
485 | ||
486 | if empty_return_check_state == RETURN_CHECK_AWAITING_BRACE: | |
487 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE | |
488 | return True | |
489 | ||
490 | return False | |
491 | ||
492 | ||
8503a516 FL |
493 | file_checks = [ |
494 | {'regex': __regex_added_doc_rst, | |
495 | 'check': check_new_docs_index}, | |
496 | {'regex': __regex_added_doc_rst, | |
497 | 'check': check_doc_docs_automake} | |
498 | ] | |
499 | ||
517f04ad AC |
500 | checks = [ |
501 | {'regex': None, | |
09b94206 | 502 | 'match_name': lambda x: not line_length_blacklist.search(x), |
860ffe70 | 503 | 'check': lambda x: line_length_check(x)}, |
907848bd | 504 | |
ae9c0985 | 505 | {'regex': None, |
09b94206 | 506 | 'match_name': lambda x: not leading_whitespace_blacklist.search(x), |
907848bd | 507 | 'check': lambda x: not leading_whitespace_is_spaces(x), |
ebba2af6 | 508 | 'print': lambda: print_warning("Line has non-spaces leading whitespace")}, |
907848bd AC |
509 | |
510 | {'regex': None, 'match_name': None, | |
511 | 'check': lambda x: trailing_whitespace_or_crlf(x), | |
ebba2af6 | 512 | 'print': lambda: print_warning("Line has trailing whitespace")}, |
907848bd | 513 | |
bcd93351 | 514 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 515 | 'prereq': lambda x: not is_comment_line(x), |
907848bd | 516 | 'check': lambda x: not if_and_for_whitespace_checks(x), |
ebba2af6 | 517 | 'print': lambda: print_error("Improper whitespace around control block")}, |
907848bd | 518 | |
bcd93351 | 519 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 520 | 'prereq': lambda x: not is_comment_line(x), |
907848bd | 521 | 'check': lambda x: not if_and_for_end_with_bracket_check(x), |
ebba2af6 | 522 | 'print': lambda: print_error("Inappropriate bracing around statement")}, |
907848bd | 523 | |
bcd93351 | 524 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, |
d6ec6c10 | 525 | 'prereq': lambda x: not is_comment_line(x), |
907848bd AC |
526 | 'check': lambda x: pointer_whitespace_check(x), |
527 | 'print': | |
12f62e9d JS |
528 | lambda: print_error("Inappropriate spacing in pointer declaration")}, |
529 | ||
530 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, | |
531 | 'prereq': lambda x: not is_comment_line(x), | |
532 | 'check': lambda x: trailing_operator(x), | |
533 | 'print': | |
534 | lambda: print_error("Line has '?' or ':' operator at end of line")}, | |
4e99b70d JP |
535 | |
536 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, | |
537 | 'prereq': lambda x: has_comment(x), | |
538 | 'check': lambda x: has_xxx_mark(x), | |
539 | 'print': lambda: print_warning("Comment with 'xxx' marker")}, | |
999c7773 AC |
540 | |
541 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, | |
542 | 'prereq': lambda x: has_comment(x), | |
860ffe70 | 543 | 'check': lambda x: check_comment_spelling(x)}, |
a9e5ac0f BS |
544 | |
545 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, | |
546 | 'check': lambda x: empty_return_with_brace(x), | |
547 | 'interim_line': True, | |
548 | 'print': | |
549 | lambda: print_warning("Empty return followed by brace, consider omitting") | |
550 | }, | |
517f04ad AC |
551 | ] |
552 | ||
553 | ||
b95d82bf | 554 | def regex_function_factory(func_name): |
6ecf961e | 555 | regex = re.compile(r'\b%s\([^)]*\)' % func_name) |
b95d82bf JS |
556 | return lambda x: regex.search(x) is not None |
557 | ||
558 | ||
559 | def regex_error_factory(description): | |
560 | return lambda: print_error(description) | |
561 | ||
562 | ||
563 | std_functions = [ | |
564 | ('malloc', 'Use xmalloc() in place of malloc()'), | |
565 | ('calloc', 'Use xcalloc() in place of calloc()'), | |
566 | ('realloc', 'Use xrealloc() in place of realloc()'), | |
567 | ('strdup', 'Use xstrdup() in place of strdup()'), | |
568 | ('asprintf', 'Use xasprintf() in place of asprintf()'), | |
569 | ('vasprintf', 'Use xvasprintf() in place of vasprintf()'), | |
570 | ('strcpy', 'Use ovs_strlcpy() in place of strcpy()'), | |
571 | ('strlcpy', 'Use ovs_strlcpy() in place of strlcpy()'), | |
572 | ('strncpy', 'Use ovs_strzcpy() in place of strncpy()'), | |
573 | ('strerror', 'Use ovs_strerror() in place of strerror()'), | |
574 | ('sleep', 'Use xsleep() in place of sleep()'), | |
575 | ('abort', 'Use ovs_abort() in place of abort()'), | |
d9d849fe | 576 | ('assert', 'Use ovs_assert() in place of assert()'), |
b95d82bf JS |
577 | ('error', 'Use ovs_error() in place of error()'), |
578 | ] | |
579 | checks += [ | |
bcd93351 | 580 | {'regex': '(\.c|\.h)(\.in)?$', |
b95d82bf | 581 | 'match_name': None, |
d6ec6c10 | 582 | 'prereq': lambda x: not is_comment_line(x), |
b95d82bf JS |
583 | 'check': regex_function_factory(function_name), |
584 | 'print': regex_error_factory(description)} | |
585 | for (function_name, description) in std_functions] | |
586 | ||
587 | ||
0d7b16da JS |
588 | def regex_operator_factory(operator): |
589 | regex = re.compile(r'^[^#][^"\']*[^ "]%s[^ "\'][^"]*' % operator) | |
8361e647 | 590 | return lambda x: regex.search(filter_comments(x)) is not None |
0d7b16da JS |
591 | |
592 | ||
593 | infix_operators = \ | |
bbb2cb20 | 594 | [re.escape(op) for op in ['%', '<<', '>>', '<=', '>=', '==', '!=', |
0d7b16da JS |
595 | '^', '|', '&&', '||', '?:', '=', '+=', '-=', '*=', '/=', '%=', |
596 | '&=', '^=', '|=', '<<=', '>>=']] \ | |
597 | + ['[^<" ]<[^=" ]', '[^->" ]>[^=" ]', '[^ !()/"]\*[^/]', '[^ !&()"]&', | |
bbb2cb20 | 598 | '[^" +(]\+[^"+;]', '[^" -(]-[^"->;]', '[^" <>=!^|+\-*/%&]=[^"=]', |
0056086d | 599 | '[^* ]/[^* ]'] |
0d7b16da JS |
600 | checks += [ |
601 | {'regex': '(\.c|\.h)(\.in)?$', 'match_name': None, | |
602 | 'prereq': lambda x: not is_comment_line(x), | |
603 | 'check': regex_operator_factory(operator), | |
604 | 'print': lambda: print_warning("Line lacks whitespace around operator")} | |
605 | for operator in infix_operators] | |
606 | ||
607 | ||
517f04ad AC |
608 | def get_file_type_checks(filename): |
609 | """Returns the list of checks for a file based on matching the filename | |
610 | against regex.""" | |
611 | global checks | |
612 | checkList = [] | |
613 | for check in checks: | |
614 | if check['regex'] is None and check['match_name'] is None: | |
615 | checkList.append(check) | |
616 | if check['regex'] is not None and \ | |
617 | re.compile(check['regex']).search(filename) is not None: | |
618 | checkList.append(check) | |
619 | elif check['match_name'] is not None and check['match_name'](filename): | |
620 | checkList.append(check) | |
621 | return checkList | |
622 | ||
623 | ||
624 | def run_checks(current_file, line, lineno): | |
625 | """Runs the various checks for the particular line. This will take | |
626 | filename into account.""" | |
ebba2af6 | 627 | global checking_file, total_line |
a84a1edb | 628 | print_line = False |
517f04ad | 629 | for check in get_file_type_checks(current_file): |
d6ec6c10 BP |
630 | if 'prereq' in check and not check['prereq'](line): |
631 | continue | |
517f04ad | 632 | if check['check'](line): |
860ffe70 BP |
633 | if 'print' in check: |
634 | check['print']() | |
a84a1edb AC |
635 | print_line = True |
636 | ||
637 | if print_line: | |
ebba2af6 AC |
638 | if checking_file: |
639 | print("%s:%d:" % (current_file, lineno)) | |
640 | else: | |
641 | print("#%d FILE: %s:%d:" % (total_line, current_file, lineno)) | |
642 | print("%s\n" % line) | |
517f04ad AC |
643 | |
644 | ||
a9e5ac0f BS |
645 | def interim_line_check(current_file, line, lineno): |
646 | """Runs the various checks for the particular interim line. This will | |
647 | take filename into account, and will check for the 'interim_line' | |
648 | key before running the check.""" | |
649 | global checking_file, total_line | |
650 | print_line = False | |
651 | for check in get_file_type_checks(current_file): | |
652 | if 'prereq' in check and not check['prereq'](line): | |
653 | continue | |
654 | if 'interim_line' in check and check['interim_line']: | |
655 | if check['check'](line): | |
656 | if 'print' in check: | |
657 | check['print']() | |
658 | print_line = True | |
659 | ||
660 | if print_line: | |
661 | if checking_file: | |
662 | print("%s:%d:" % (current_file, lineno)) | |
663 | else: | |
664 | print("#%d FILE: %s:%d:" % (total_line, current_file, lineno)) | |
665 | print("%s\n" % line) | |
666 | ||
667 | ||
8503a516 FL |
668 | def run_file_checks(text): |
669 | """Runs the various checks for the text.""" | |
670 | for check in file_checks: | |
671 | if check['regex'].search(text) is not None: | |
672 | check['check'](text) | |
673 | ||
674 | ||
3267343a | 675 | def ovs_checkpatch_parse(text, filename, author=None, committer=None): |
a9e5ac0f BS |
676 | global print_file_name, total_line, checking_file, \ |
677 | empty_return_check_state | |
6ed80686 AC |
678 | |
679 | PARSE_STATE_HEADING = 0 | |
680 | PARSE_STATE_DIFF_HEADER = 1 | |
681 | PARSE_STATE_CHANGE_BODY = 2 | |
682 | ||
c599d5cc AC |
683 | lineno = 0 |
684 | signatures = [] | |
685 | co_authors = [] | |
686 | parse = 0 | |
95bd35d3 | 687 | current_file = filename if checking_file else '' |
6982ee96 | 688 | previous_file = '' |
128b46a0 | 689 | seppatch = re.compile(r'^---([\w]*| \S+)$') |
c599d5cc | 690 | hunks = re.compile('^(---|\+\+\+) (\S+)') |
4d7f5e51 AC |
691 | hunk_differences = re.compile( |
692 | r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@') | |
3267343a BP |
693 | is_author = re.compile(r'^(Author|From): (.*)$', re.I | re.M | re.S) |
694 | is_committer = re.compile(r'^(Commit: )(.*)$', re.I | re.M | re.S) | |
3ccb8899 | 695 | is_signature = re.compile(r'^(Signed-off-by: )(.*)$', |
c599d5cc | 696 | re.I | re.M | re.S) |
3ccb8899 | 697 | is_co_author = re.compile(r'^(Co-authored-by: )(.*)$', |
c599d5cc | 698 | re.I | re.M | re.S) |
bc03d850 IM |
699 | is_gerrit_change_id = re.compile(r'(\s*(change-id: )(.*))$', |
700 | re.I | re.M | re.S) | |
c599d5cc | 701 | |
81c2316f IM |
702 | reset_counters() |
703 | ||
822a3d88 | 704 | for line in text.split('\n'): |
6982ee96 JS |
705 | if current_file != previous_file: |
706 | previous_file = current_file | |
6982ee96 | 707 | |
c599d5cc | 708 | lineno = lineno + 1 |
ebba2af6 | 709 | total_line = total_line + 1 |
c599d5cc AC |
710 | if len(line) <= 0: |
711 | continue | |
712 | ||
fb9410d8 | 713 | if checking_file: |
6ed80686 | 714 | parse = PARSE_STATE_CHANGE_BODY |
fb9410d8 | 715 | |
6ed80686 | 716 | if parse == PARSE_STATE_DIFF_HEADER: |
c599d5cc AC |
717 | match = hunks.match(line) |
718 | if match: | |
6ed80686 | 719 | parse = PARSE_STATE_CHANGE_BODY |
2797ff00 | 720 | current_file = match.group(2)[2:] |
3239c793 | 721 | print_file_name = current_file |
c599d5cc | 722 | continue |
6ed80686 | 723 | elif parse == PARSE_STATE_HEADING: |
128b46a0 | 724 | if seppatch.match(line): |
6ed80686 | 725 | parse = PARSE_STATE_DIFF_HEADER |
c599d5cc | 726 | if not skip_signoff_check: |
3267343a BP |
727 | |
728 | # Check that the patch has an author, that the | |
729 | # author is not among the co-authors, and that the | |
730 | # co-authors are unique. | |
731 | if not author: | |
732 | print_error("Patch lacks author.") | |
733 | continue | |
3bd2e465 BP |
734 | if " via " in author or "@openvswitch.org" in author: |
735 | print_error("Author should not be mailing list.") | |
736 | continue | |
3267343a BP |
737 | if author in co_authors: |
738 | print_error("Author should not be also be co-author.") | |
739 | continue | |
740 | if len(set(co_authors)) != len(co_authors): | |
741 | print_error("Duplicate co-author.") | |
742 | ||
743 | # Check that the author, all co-authors, and the | |
744 | # committer (if any) signed off. | |
745 | if author not in signatures: | |
746 | print_error("Author %s needs to sign off." % author) | |
747 | for ca in co_authors: | |
748 | if ca not in signatures: | |
749 | print_error("Co-author %s needs to sign off." % ca) | |
750 | break | |
751 | if (committer | |
752 | and author != committer | |
753 | and committer not in signatures): | |
754 | print_error("Committer %s needs to sign off." | |
755 | % committer) | |
756 | ||
757 | # Check for signatures that we do not expect. | |
758 | # This is only a warning because there can be, | |
759 | # rarely, a signature chain. | |
760 | # | |
761 | # If we don't have a known committer, and there is | |
762 | # a single extra sign-off, then do not warn | |
763 | # because that extra sign-off is probably the | |
764 | # committer. | |
765 | extra_sigs = [x for x in signatures | |
766 | if x not in co_authors | |
767 | and x != author | |
768 | and x != committer] | |
769 | if len(extra_sigs) > 1 or (committer and extra_sigs): | |
770 | print_warning("Unexpected sign-offs from developers " | |
771 | "who are not authors or co-authors or " | |
772 | "committers: %s" | |
773 | % ", ".join(extra_sigs)) | |
774 | elif is_committer.match(line): | |
775 | committer = is_committer.match(line).group(2) | |
776 | elif is_author.match(line): | |
777 | author = is_author.match(line).group(2) | |
c599d5cc AC |
778 | elif is_signature.match(line): |
779 | m = is_signature.match(line) | |
3ccb8899 | 780 | signatures.append(m.group(2)) |
c599d5cc AC |
781 | elif is_co_author.match(line): |
782 | m = is_co_author.match(line) | |
3ccb8899 | 783 | co_authors.append(m.group(2)) |
bc03d850 IM |
784 | elif is_gerrit_change_id.match(line): |
785 | print_error( | |
786 | "Remove Gerrit Change-Id's before submitting upstream.") | |
787 | print("%d: %s\n" % (lineno, line)) | |
6ed80686 | 788 | elif parse == PARSE_STATE_CHANGE_BODY: |
c599d5cc AC |
789 | newfile = hunks.match(line) |
790 | if newfile: | |
bbbe2fa2 | 791 | current_file = newfile.group(2)[2:] |
3239c793 | 792 | print_file_name = current_file |
c599d5cc | 793 | continue |
4d7f5e51 AC |
794 | reset_line_number = hunk_differences.match(line) |
795 | if reset_line_number: | |
a9e5ac0f | 796 | empty_return_check_state = RETURN_CHECK_INITIAL_STATE |
4d7f5e51 AC |
797 | lineno = int(reset_line_number.group(3)) |
798 | if lineno < 0: | |
799 | lineno = -1 * lineno | |
800 | lineno -= 1 | |
a9e5ac0f | 801 | |
4d7f5e51 AC |
802 | if is_subtracted_line(line): |
803 | lineno -= 1 | |
c599d5cc | 804 | continue |
fb9410d8 AC |
805 | |
806 | cmp_line = added_line(line) | |
807 | ||
a9e5ac0f BS |
808 | if not is_added_line(line): |
809 | interim_line_check(current_file, cmp_line, lineno) | |
810 | continue | |
811 | ||
c599d5cc AC |
812 | # Skip files which have /datapath in them, since they are |
813 | # linux or windows coding standards | |
2797ff00 | 814 | if current_file.startswith('datapath'): |
c599d5cc | 815 | continue |
4f74db48 JS |
816 | if current_file.startswith('include/linux'): |
817 | continue | |
517f04ad | 818 | run_checks(current_file, cmp_line, lineno) |
8503a516 FL |
819 | |
820 | run_file_checks(text) | |
c599d5cc AC |
821 | if __errors or __warnings: |
822 | return -1 | |
823 | return 0 | |
824 | ||
825 | ||
826 | def usage(): | |
a1fccabc BP |
827 | print("""\ |
828 | Open vSwitch checkpatch.py | |
829 | Checks a patch for trivial mistakes. | |
830 | usage: | |
b90cfa86 | 831 | %s [options] [PATCH1 [PATCH2 ...] | -f SOURCE1 [SOURCE2 ...] | -1 | -2 | ...] |
a1fccabc BP |
832 | |
833 | Input options: | |
834 | -f|--check-file Arguments are source files, not patches. | |
835 | -1, -2, ... Check recent commits in this repo. | |
836 | ||
837 | Check options: | |
838 | -h|--help This help message | |
839 | -b|--skip-block-whitespace Skips the if/while/for whitespace tests | |
840 | -l|--skip-leading-whitespace Skips the leading whitespace test | |
de8fa82a | 841 | -q|--quiet Only print error and warning information |
a1fccabc | 842 | -s|--skip-signoff-lines Tolerate missing Signed-off-by line |
999c7773 | 843 | -S|--spellcheck-comments Check C comments for possible spelling mistakes |
a1fccabc BP |
844 | -t|--skip-trailing-whitespace Skips the trailing whitespace test""" |
845 | % sys.argv[0]) | |
c599d5cc | 846 | |
a5e9c53c | 847 | |
81c2316f | 848 | def ovs_checkpatch_print_result(result): |
de8fa82a AC |
849 | global quiet, __warnings, __errors, total_line |
850 | ||
81c2316f IM |
851 | if result < 0: |
852 | print("Lines checked: %d, Warnings: %d, Errors: %d\n" % | |
853 | (total_line, __warnings, __errors)) | |
de8fa82a | 854 | elif not quiet: |
81c2316f IM |
855 | print("Lines checked: %d, no obvious problems found\n" % (total_line)) |
856 | ||
857 | ||
c599d5cc AC |
858 | def ovs_checkpatch_file(filename): |
859 | try: | |
860 | mail = email.message_from_file(open(filename, 'r')) | |
861 | except: | |
862 | print_error("Unable to parse file '%s'. Is it a patch?" % filename) | |
863 | return -1 | |
864 | ||
865 | for part in mail.walk(): | |
866 | if part.get_content_maintype() == 'multipart': | |
867 | continue | |
3267343a BP |
868 | result = ovs_checkpatch_parse(part.get_payload(decode=False), filename, |
869 | mail.get('Author', mail['From']), | |
870 | mail['Commit']) | |
81c2316f | 871 | ovs_checkpatch_print_result(result) |
7d6b834f | 872 | return result |
c599d5cc | 873 | |
884e0dfe | 874 | |
a1fccabc BP |
875 | def partition(pred, iterable): |
876 | """Returns [[trues], [falses]], where [trues] is the items in | |
877 | 'iterable' that satisfy 'pred' and [falses] is all the rest.""" | |
878 | trues = [] | |
879 | falses = [] | |
880 | for item in iterable: | |
881 | if pred(item): | |
882 | trues.append(item) | |
883 | else: | |
884 | falses.append(item) | |
885 | return trues, falses | |
886 | ||
887 | ||
c599d5cc AC |
888 | if __name__ == '__main__': |
889 | try: | |
a1fccabc BP |
890 | numeric_options, args = partition(lambda s: re.match('-[0-9]+$', s), |
891 | sys.argv[1:]) | |
892 | n_patches = int(numeric_options[-1][1:]) if numeric_options else 0 | |
893 | ||
de8fa82a | 894 | optlist, args = getopt.getopt(args, 'bhlstfSq', |
fb9410d8 AC |
895 | ["check-file", |
896 | "help", | |
c599d5cc AC |
897 | "skip-block-whitespace", |
898 | "skip-leading-whitespace", | |
899 | "skip-signoff-lines", | |
999c7773 | 900 | "skip-trailing-whitespace", |
de8fa82a AC |
901 | "spellcheck-comments", |
902 | "quiet"]) | |
c599d5cc AC |
903 | except: |
904 | print("Unknown option encountered. Please rerun with -h for help.") | |
905 | sys.exit(-1) | |
906 | ||
907 | for o, a in optlist: | |
908 | if o in ("-h", "--help"): | |
909 | usage() | |
910 | sys.exit(0) | |
911 | elif o in ("-b", "--skip-block-whitespace"): | |
912 | skip_block_whitespace_check = True | |
913 | elif o in ("-l", "--skip-leading-whitespace"): | |
914 | skip_leading_whitespace_check = True | |
915 | elif o in ("-s", "--skip-signoff-lines"): | |
916 | skip_signoff_check = True | |
917 | elif o in ("-t", "--skip-trailing-whitespace"): | |
918 | skip_trailing_whitespace_check = True | |
fb9410d8 AC |
919 | elif o in ("-f", "--check-file"): |
920 | checking_file = True | |
999c7773 AC |
921 | elif o in ("-S", "--spellcheck-comments"): |
922 | if no_spellcheck: | |
923 | print("WARNING: The enchant library isn't availble.") | |
924 | print(" Please install python enchant.") | |
925 | else: | |
926 | spellcheck_comments = True | |
de8fa82a AC |
927 | elif o in ("-q", "--quiet"): |
928 | quiet = True | |
c599d5cc AC |
929 | else: |
930 | print("Unknown option '%s'" % o) | |
931 | sys.exit(-1) | |
ebba2af6 AC |
932 | |
933 | if sys.stdout.isatty(): | |
934 | colors = True | |
935 | ||
a1fccabc BP |
936 | if n_patches: |
937 | status = 0 | |
d962bad2 IM |
938 | |
939 | git_log = 'git log --no-color --no-merges --pretty=format:"%H %s" ' | |
940 | with os.popen(git_log + '-%d' % n_patches, 'r') as f: | |
941 | commits = f.read().split("\n") | |
942 | ||
057653ab | 943 | for i in reversed(range(0, n_patches)): |
d962bad2 | 944 | revision, name = commits[i].split(" ", 1) |
3267343a BP |
945 | f = os.popen('''git format-patch -1 --stdout --pretty=format:"\ |
946 | Author: %an <%ae> | |
947 | Commit: %cn <%ce> | |
948 | Subject: %s | |
949 | ||
950 | %b" ''' + revision, 'r') | |
a1fccabc BP |
951 | patch = f.read() |
952 | f.close() | |
953 | ||
de8fa82a AC |
954 | if not quiet: |
955 | print('== Checking %s ("%s") ==' % (revision[0:12], name)) | |
81c2316f IM |
956 | result = ovs_checkpatch_parse(patch, revision) |
957 | ovs_checkpatch_print_result(result) | |
958 | if result: | |
a1fccabc BP |
959 | status = -1 |
960 | sys.exit(status) | |
961 | ||
b90cfa86 | 962 | if not args: |
c599d5cc AC |
963 | if sys.stdin.isatty(): |
964 | usage() | |
965 | sys.exit(-1) | |
81c2316f IM |
966 | result = ovs_checkpatch_parse(sys.stdin.read(), '-') |
967 | ovs_checkpatch_print_result(result) | |
968 | sys.exit(result) | |
b90cfa86 IM |
969 | |
970 | status = 0 | |
971 | for filename in args: | |
de8fa82a AC |
972 | if not quiet: |
973 | print('== Checking "%s" ==' % filename) | |
b90cfa86 IM |
974 | result = ovs_checkpatch_file(filename) |
975 | if result: | |
976 | status = -1 | |
977 | sys.exit(status) |