]>
Commit | Line | Data |
---|---|---|
c599d5cc AC |
1 | #!/usr/bin/env python |
2 | # Copyright (c) 2016 Red Hat, Inc. | |
3 | # | |
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: | |
7 | # | |
8 | # http://www.apache.org/licenses/LICENSE-2.0 | |
9 | # | |
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 | |
16 | ||
17 | import email | |
18 | import getopt | |
19 | import re | |
20 | import sys | |
21 | ||
22 | __errors = 0 | |
23 | __warnings = 0 | |
3239c793 | 24 | print_file_name = None |
fb9410d8 | 25 | checking_file = False |
3239c793 AC |
26 | |
27 | ||
28 | def print_file(): | |
29 | global print_file_name | |
30 | if print_file_name: | |
31 | print("In file %s" % print_file_name) | |
32 | print_file_name = None | |
c599d5cc AC |
33 | |
34 | ||
35 | def print_error(message, lineno=None): | |
36 | global __errors | |
3239c793 | 37 | print_file() |
c599d5cc AC |
38 | if lineno is not None: |
39 | print("E(%d): %s" % (lineno, message)) | |
40 | else: | |
41 | print("E: %s" % (message)) | |
42 | ||
43 | __errors = __errors + 1 | |
44 | ||
45 | ||
46 | def print_warning(message, lineno=None): | |
47 | global __warnings | |
3239c793 | 48 | print_file() |
c599d5cc AC |
49 | if lineno: |
50 | print("W(%d): %s" % (lineno, message)) | |
51 | else: | |
52 | print("W: %s" % (message)) | |
53 | ||
54 | __warnings = __warnings + 1 | |
55 | ||
56 | ||
57 | __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*') | |
4d7f5e51 | 58 | __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*') |
c599d5cc AC |
59 | __regex_leading_with_whitespace_at_all = re.compile(r'^\s+') |
60 | __regex_leading_with_spaces = re.compile(r'^ +[\S]+') | |
61 | __regex_trailing_whitespace = re.compile(r'[^\S]+$') | |
c61e93d6 | 62 | __regex_single_line_feed = re.compile(r'^\f$') |
a1193b4d AC |
63 | __regex_for_if_missing_whitespace = re.compile(r' +(if|for|while)[\(]') |
64 | __regex_for_if_too_much_whitespace = re.compile(r' +(if|for|while) +[\(]') | |
65 | __regex_for_if_parens_whitespace = \ | |
66 | re.compile(r' +(if|for|while) \( +[\s\S]+\)') | |
30c7ffd5 AC |
67 | __regex_is_for_if_single_line_bracket = \ |
68 | re.compile(r'^ +(if|for|while) \(.*\)') | |
a1193b4d AC |
69 | __regex_ends_with_bracket = \ |
70 | re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$') | |
6fc2799e | 71 | __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*') |
c599d5cc AC |
72 | |
73 | skip_leading_whitespace_check = False | |
74 | skip_trailing_whitespace_check = False | |
75 | skip_block_whitespace_check = False | |
76 | skip_signoff_check = False | |
77 | ||
6982ee96 JS |
78 | # Don't enforce character limit on files that include these characters in their |
79 | # name, as they may have legitimate reasons to have longer lines. | |
80 | # | |
81 | # Python isn't checked as flake8 performs these checks during build. | |
82 | line_length_blacklist = ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch', | |
83 | '.py'] | |
84 | ||
c599d5cc | 85 | |
4d7f5e51 AC |
86 | def is_subtracted_line(line): |
87 | """Returns TRUE if the line in question has been removed.""" | |
88 | return __regex_subtracted_line.search(line) is not None | |
89 | ||
fb9410d8 | 90 | |
c599d5cc AC |
91 | def is_added_line(line): |
92 | """Returns TRUE if the line in question is an added line. | |
93 | """ | |
fb9410d8 AC |
94 | global checking_file |
95 | return __regex_added_line.search(line) is not None or checking_file | |
96 | ||
97 | ||
98 | def added_line(line): | |
99 | """Returns the line formatted properly by removing diff syntax""" | |
100 | global checking_file | |
101 | if not checking_file: | |
102 | return line[1:] | |
103 | return line | |
c599d5cc AC |
104 | |
105 | ||
106 | def leading_whitespace_is_spaces(line): | |
107 | """Returns TRUE if the leading whitespace in added lines is spaces | |
108 | """ | |
109 | if skip_leading_whitespace_check: | |
110 | return True | |
c61e93d6 DDP |
111 | if (__regex_leading_with_whitespace_at_all.search(line) is not None and |
112 | __regex_single_line_feed.search(line) is None): | |
c599d5cc | 113 | return __regex_leading_with_spaces.search(line) is not None |
c61e93d6 | 114 | |
c599d5cc AC |
115 | return True |
116 | ||
117 | ||
118 | def trailing_whitespace_or_crlf(line): | |
119 | """Returns TRUE if the trailing characters is whitespace | |
120 | """ | |
121 | if skip_trailing_whitespace_check: | |
122 | return False | |
c61e93d6 DDP |
123 | return (__regex_trailing_whitespace.search(line) is not None and |
124 | __regex_single_line_feed.search(line) is None) | |
c599d5cc AC |
125 | |
126 | ||
127 | def if_and_for_whitespace_checks(line): | |
128 | """Return TRUE if there is appropriate whitespace after if, for, while | |
129 | """ | |
130 | if skip_block_whitespace_check: | |
131 | return True | |
132 | if (__regex_for_if_missing_whitespace.search(line) is not None or | |
133 | __regex_for_if_too_much_whitespace.search(line) is not None or | |
134 | __regex_for_if_parens_whitespace.search(line)): | |
135 | return False | |
136 | return True | |
137 | ||
138 | ||
30c7ffd5 AC |
139 | def if_and_for_end_with_bracket_check(line): |
140 | """Return TRUE if there is not a bracket at the end of an if, for, while | |
141 | block which fits on a single line ie: 'if (foo)'""" | |
142 | ||
143 | def balanced_parens(line): | |
144 | """This is a rather naive counter - it won't deal with quotes""" | |
145 | balance = 0 | |
146 | for letter in line: | |
973496b9 | 147 | if letter == '(': |
30c7ffd5 | 148 | balance += 1 |
973496b9 | 149 | elif letter == ')': |
30c7ffd5 | 150 | balance -= 1 |
973496b9 | 151 | return balance == 0 |
30c7ffd5 AC |
152 | |
153 | if __regex_is_for_if_single_line_bracket.search(line) is not None: | |
154 | if not balanced_parens(line): | |
155 | return True | |
156 | if __regex_ends_with_bracket.search(line) is None: | |
157 | return False | |
158 | return True | |
159 | ||
160 | ||
6fc2799e JS |
161 | def pointer_whitespace_check(line): |
162 | """Return TRUE if there is no space between a pointer name and the | |
163 | asterisk that denotes this is a apionter type, ie: 'struct foo*'""" | |
164 | return __regex_ptr_declaration_missing_whitespace.search(line) is not None | |
165 | ||
166 | ||
c599d5cc | 167 | def ovs_checkpatch_parse(text): |
3239c793 | 168 | global print_file_name |
c599d5cc AC |
169 | lineno = 0 |
170 | signatures = [] | |
171 | co_authors = [] | |
172 | parse = 0 | |
173 | current_file = '' | |
6982ee96 | 174 | previous_file = '' |
c599d5cc AC |
175 | scissors = re.compile(r'^[\w]*---[\w]*') |
176 | hunks = re.compile('^(---|\+\+\+) (\S+)') | |
4d7f5e51 AC |
177 | hunk_differences = re.compile( |
178 | r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@') | |
c599d5cc AC |
179 | is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$', |
180 | re.I | re.M | re.S) | |
181 | is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$', | |
182 | re.I | re.M | re.S) | |
6982ee96 | 183 | skip_line_length_check = False |
c599d5cc | 184 | |
bddbdd43 | 185 | for line in text.decode().split('\n'): |
6982ee96 JS |
186 | if current_file != previous_file: |
187 | previous_file = current_file | |
188 | if any([fmt in current_file for fmt in line_length_blacklist]): | |
189 | skip_line_length_check = True | |
190 | else: | |
191 | skip_line_length_check = False | |
192 | ||
c599d5cc AC |
193 | lineno = lineno + 1 |
194 | if len(line) <= 0: | |
195 | continue | |
196 | ||
fb9410d8 AC |
197 | if checking_file: |
198 | parse = 2 | |
199 | ||
c599d5cc AC |
200 | if parse == 1: |
201 | match = hunks.match(line) | |
202 | if match: | |
203 | parse = parse + 1 | |
204 | current_file = match.group(2) | |
3239c793 | 205 | print_file_name = current_file |
c599d5cc AC |
206 | continue |
207 | elif parse == 0: | |
208 | if scissors.match(line): | |
209 | parse = parse + 1 | |
210 | if not skip_signoff_check: | |
211 | if len(signatures) == 0: | |
212 | print_error("No signatures found.") | |
83f76d4e | 213 | elif len(signatures) != 1 + len(co_authors): |
c599d5cc AC |
214 | print_error("Too many signoffs; " |
215 | "are you missing Co-authored-by lines?") | |
216 | if not set(co_authors) <= set(signatures): | |
217 | print_error("Co-authored-by/Signed-off-by corruption") | |
218 | elif is_signature.match(line): | |
219 | m = is_signature.match(line) | |
220 | signatures.append(m.group(3)) | |
221 | elif is_co_author.match(line): | |
222 | m = is_co_author.match(line) | |
223 | co_authors.append(m.group(3)) | |
224 | elif parse == 2: | |
225 | print_line = False | |
226 | newfile = hunks.match(line) | |
227 | if newfile: | |
228 | current_file = newfile.group(2) | |
3239c793 | 229 | print_file_name = current_file |
c599d5cc | 230 | continue |
4d7f5e51 AC |
231 | reset_line_number = hunk_differences.match(line) |
232 | if reset_line_number: | |
233 | lineno = int(reset_line_number.group(3)) | |
234 | if lineno < 0: | |
235 | lineno = -1 * lineno | |
236 | lineno -= 1 | |
237 | if is_subtracted_line(line): | |
238 | lineno -= 1 | |
c599d5cc AC |
239 | if not is_added_line(line): |
240 | continue | |
fb9410d8 AC |
241 | |
242 | cmp_line = added_line(line) | |
243 | ||
c599d5cc AC |
244 | # Skip files which have /datapath in them, since they are |
245 | # linux or windows coding standards | |
246 | if '/datapath' in current_file: | |
247 | continue | |
248 | if (not current_file.endswith('.mk') and | |
fb9410d8 | 249 | not leading_whitespace_is_spaces(cmp_line)): |
c599d5cc AC |
250 | print_line = True |
251 | print_warning("Line has non-spaces leading whitespace", | |
252 | lineno) | |
fb9410d8 | 253 | if trailing_whitespace_or_crlf(cmp_line): |
c599d5cc AC |
254 | print_line = True |
255 | print_warning("Line has trailing whitespace", lineno) | |
fb9410d8 | 256 | if len(cmp_line) > 79 and not skip_line_length_check: |
c599d5cc AC |
257 | print_line = True |
258 | print_warning("Line is greater than 79-characters long", | |
259 | lineno) | |
fb9410d8 | 260 | if not if_and_for_whitespace_checks(cmp_line): |
c599d5cc | 261 | print_line = True |
e3ecc9d3 AC |
262 | print_error("Improper whitespace around control block", |
263 | lineno) | |
fb9410d8 | 264 | if not if_and_for_end_with_bracket_check(cmp_line): |
30c7ffd5 | 265 | print_line = True |
e3ecc9d3 | 266 | print_error("Inappropriate bracing around statement", lineno) |
6fc2799e JS |
267 | if pointer_whitespace_check(cmp_line): |
268 | print_line = True | |
269 | print_error("Inappropriate spacing in pointer declaration", | |
270 | lineno) | |
c599d5cc | 271 | if print_line: |
e3ecc9d3 | 272 | print("\n%s\n" % line) |
c599d5cc AC |
273 | if __errors or __warnings: |
274 | return -1 | |
275 | return 0 | |
276 | ||
277 | ||
278 | def usage(): | |
279 | print("Open vSwitch checkpatch.py") | |
280 | print("Checks a patch for trivial mistakes.") | |
281 | print("usage:") | |
282 | print("%s [options] [patch file]" % sys.argv[0]) | |
283 | print("options:") | |
284 | print("-h|--help\t\t\t\tThis help message") | |
285 | print("-b|--skip-block-whitespace\t" | |
286 | "Skips the if/while/for whitespace tests") | |
fb9410d8 | 287 | print("-f|--check-file\t\t\tCheck a file instead of a patchfile.") |
c599d5cc AC |
288 | print("-l|--skip-leading-whitespace\t" |
289 | "Skips the leading whitespace test") | |
290 | print("-s|--skip-signoff-lines\t" | |
291 | "Do not emit an error if no Signed-off-by line is present") | |
292 | print("-t|--skip-trailing-whitespace\t" | |
293 | "Skips the trailing whitespace test") | |
294 | ||
a5e9c53c | 295 | |
c599d5cc | 296 | def ovs_checkpatch_file(filename): |
fb9410d8 | 297 | global __warnings, __errors, checking_file |
c599d5cc AC |
298 | try: |
299 | mail = email.message_from_file(open(filename, 'r')) | |
300 | except: | |
301 | print_error("Unable to parse file '%s'. Is it a patch?" % filename) | |
302 | return -1 | |
303 | ||
304 | for part in mail.walk(): | |
305 | if part.get_content_maintype() == 'multipart': | |
306 | continue | |
7d6b834f AC |
307 | result = ovs_checkpatch_parse(part.get_payload(decode=True)) |
308 | if result < 0: | |
309 | print("Warnings: %d, Errors: %d" % (__warnings, __errors)) | |
310 | return result | |
c599d5cc | 311 | |
884e0dfe | 312 | |
c599d5cc AC |
313 | if __name__ == '__main__': |
314 | try: | |
fb9410d8 AC |
315 | optlist, args = getopt.getopt(sys.argv[1:], 'bhlstf', |
316 | ["check-file", | |
317 | "help", | |
c599d5cc AC |
318 | "skip-block-whitespace", |
319 | "skip-leading-whitespace", | |
320 | "skip-signoff-lines", | |
321 | "skip-trailing-whitespace"]) | |
322 | except: | |
323 | print("Unknown option encountered. Please rerun with -h for help.") | |
324 | sys.exit(-1) | |
325 | ||
326 | for o, a in optlist: | |
327 | if o in ("-h", "--help"): | |
328 | usage() | |
329 | sys.exit(0) | |
330 | elif o in ("-b", "--skip-block-whitespace"): | |
331 | skip_block_whitespace_check = True | |
332 | elif o in ("-l", "--skip-leading-whitespace"): | |
333 | skip_leading_whitespace_check = True | |
334 | elif o in ("-s", "--skip-signoff-lines"): | |
335 | skip_signoff_check = True | |
336 | elif o in ("-t", "--skip-trailing-whitespace"): | |
337 | skip_trailing_whitespace_check = True | |
fb9410d8 AC |
338 | elif o in ("-f", "--check-file"): |
339 | checking_file = True | |
c599d5cc AC |
340 | else: |
341 | print("Unknown option '%s'" % o) | |
342 | sys.exit(-1) | |
343 | try: | |
344 | filename = args[0] | |
345 | except: | |
346 | if sys.stdin.isatty(): | |
347 | usage() | |
348 | sys.exit(-1) | |
349 | sys.exit(ovs_checkpatch_parse(sys.stdin.read())) | |
350 | sys.exit(ovs_checkpatch_file(filename)) |