]>
Commit | Line | Data |
---|---|---|
0d24de9d SG |
1 | # Copyright (c) 2011 The Chromium OS Authors. |
2 | # | |
1a459660 | 3 | # SPDX-License-Identifier: GPL-2.0+ |
0d24de9d SG |
4 | # |
5 | ||
d29fe6e2 | 6 | import collections |
0d24de9d SG |
7 | import command |
8 | import gitutil | |
9 | import os | |
10 | import re | |
99adf6ed | 11 | import sys |
0d24de9d SG |
12 | import terminal |
13 | ||
14 | def FindCheckPatch(): | |
d96ef37d | 15 | top_level = gitutil.GetTopLevel() |
0d24de9d SG |
16 | try_list = [ |
17 | os.getcwd(), | |
18 | os.path.join(os.getcwd(), '..', '..'), | |
d96ef37d DA |
19 | os.path.join(top_level, 'tools'), |
20 | os.path.join(top_level, 'scripts'), | |
0d24de9d SG |
21 | '%s/bin' % os.getenv('HOME'), |
22 | ] | |
23 | # Look in current dir | |
24 | for path in try_list: | |
25 | fname = os.path.join(path, 'checkpatch.pl') | |
26 | if os.path.isfile(fname): | |
27 | return fname | |
28 | ||
29 | # Look upwwards for a Chrome OS tree | |
30 | while not os.path.ismount(path): | |
31 | fname = os.path.join(path, 'src', 'third_party', 'kernel', 'files', | |
32 | 'scripts', 'checkpatch.pl') | |
33 | if os.path.isfile(fname): | |
34 | return fname | |
35 | path = os.path.dirname(path) | |
99adf6ed | 36 | |
31e2141d MY |
37 | sys.exit('Cannot find checkpatch.pl - please put it in your ' + |
38 | '~/bin directory or use --no-check') | |
0d24de9d SG |
39 | |
40 | def CheckPatch(fname, verbose=False): | |
41 | """Run checkpatch.pl on a file. | |
42 | ||
43 | Returns: | |
d29fe6e2 SG |
44 | namedtuple containing: |
45 | ok: False=failure, True=ok | |
0d24de9d SG |
46 | problems: List of problems, each a dict: |
47 | 'type'; error or warning | |
48 | 'msg': text message | |
49 | 'file' : filename | |
50 | 'line': line number | |
d29fe6e2 SG |
51 | errors: Number of errors |
52 | warnings: Number of warnings | |
53 | checks: Number of checks | |
0d24de9d | 54 | lines: Number of lines |
d29fe6e2 | 55 | stdout: Full output of checkpatch |
0d24de9d | 56 | """ |
d29fe6e2 SG |
57 | fields = ['ok', 'problems', 'errors', 'warnings', 'checks', 'lines', |
58 | 'stdout'] | |
59 | result = collections.namedtuple('CheckPatchResult', fields) | |
60 | result.ok = False | |
61 | result.errors, result.warning, result.checks = 0, 0, 0 | |
62 | result.lines = 0 | |
63 | result.problems = [] | |
0d24de9d | 64 | chk = FindCheckPatch() |
0d24de9d | 65 | item = {} |
785f1548 SG |
66 | result.stdout = command.Output(chk, '--no-tree', fname, |
67 | raise_on_error=False) | |
0d24de9d SG |
68 | #pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) |
69 | #stdout, stderr = pipe.communicate() | |
70 | ||
71 | # total: 0 errors, 0 warnings, 159 lines checked | |
d29fe6e2 SG |
72 | # or: |
73 | # total: 0 errors, 2 warnings, 7 checks, 473 lines checked | |
0d24de9d | 74 | re_stats = re.compile('total: (\\d+) errors, (\d+) warnings, (\d+)') |
d29fe6e2 SG |
75 | re_stats_full = re.compile('total: (\\d+) errors, (\d+) warnings, (\d+)' |
76 | ' checks, (\d+)') | |
0d24de9d SG |
77 | re_ok = re.compile('.*has no obvious style problems') |
78 | re_bad = re.compile('.*has style problems, please review') | |
79 | re_error = re.compile('ERROR: (.*)') | |
80 | re_warning = re.compile('WARNING: (.*)') | |
d29fe6e2 | 81 | re_check = re.compile('CHECK: (.*)') |
0d24de9d SG |
82 | re_file = re.compile('#\d+: FILE: ([^:]*):(\d+):') |
83 | ||
d29fe6e2 | 84 | for line in result.stdout.splitlines(): |
0d24de9d | 85 | if verbose: |
a920a17b | 86 | print(line) |
0d24de9d SG |
87 | |
88 | # A blank line indicates the end of a message | |
89 | if not line and item: | |
d29fe6e2 | 90 | result.problems.append(item) |
0d24de9d | 91 | item = {} |
d29fe6e2 SG |
92 | match = re_stats_full.match(line) |
93 | if not match: | |
94 | match = re_stats.match(line) | |
0d24de9d | 95 | if match: |
d29fe6e2 SG |
96 | result.errors = int(match.group(1)) |
97 | result.warnings = int(match.group(2)) | |
98 | if len(match.groups()) == 4: | |
99 | result.checks = int(match.group(3)) | |
100 | result.lines = int(match.group(4)) | |
101 | else: | |
102 | result.lines = int(match.group(3)) | |
0d24de9d | 103 | elif re_ok.match(line): |
d29fe6e2 | 104 | result.ok = True |
0d24de9d | 105 | elif re_bad.match(line): |
d29fe6e2 SG |
106 | result.ok = False |
107 | err_match = re_error.match(line) | |
108 | warn_match = re_warning.match(line) | |
109 | file_match = re_file.match(line) | |
110 | check_match = re_check.match(line) | |
111 | if err_match: | |
112 | item['msg'] = err_match.group(1) | |
0d24de9d | 113 | item['type'] = 'error' |
d29fe6e2 SG |
114 | elif warn_match: |
115 | item['msg'] = warn_match.group(1) | |
0d24de9d | 116 | item['type'] = 'warning' |
d29fe6e2 SG |
117 | elif check_match: |
118 | item['msg'] = check_match.group(1) | |
119 | item['type'] = 'check' | |
120 | elif file_match: | |
121 | item['file'] = file_match.group(1) | |
122 | item['line'] = int(file_match.group(2)) | |
0d24de9d | 123 | |
d29fe6e2 | 124 | return result |
0d24de9d SG |
125 | |
126 | def GetWarningMsg(col, msg_type, fname, line, msg): | |
127 | '''Create a message for a given file/line | |
128 | ||
129 | Args: | |
130 | msg_type: Message type ('error' or 'warning') | |
131 | fname: Filename which reports the problem | |
132 | line: Line number where it was noticed | |
133 | msg: Message to report | |
134 | ''' | |
135 | if msg_type == 'warning': | |
136 | msg_type = col.Color(col.YELLOW, msg_type) | |
137 | elif msg_type == 'error': | |
138 | msg_type = col.Color(col.RED, msg_type) | |
d29fe6e2 SG |
139 | elif msg_type == 'check': |
140 | msg_type = col.Color(col.MAGENTA, msg_type) | |
8aa41363 | 141 | return '%s:%d: %s: %s\n' % (fname, line, msg_type, msg) |
0d24de9d SG |
142 | |
143 | def CheckPatches(verbose, args): | |
144 | '''Run the checkpatch.pl script on each patch''' | |
d29fe6e2 | 145 | error_count, warning_count, check_count = 0, 0, 0 |
0d24de9d SG |
146 | col = terminal.Color() |
147 | ||
148 | for fname in args: | |
d29fe6e2 SG |
149 | result = CheckPatch(fname, verbose) |
150 | if not result.ok: | |
151 | error_count += result.errors | |
152 | warning_count += result.warnings | |
153 | check_count += result.checks | |
a920a17b PB |
154 | print('%d errors, %d warnings, %d checks for %s:' % (result.errors, |
155 | result.warnings, result.checks, col.Color(col.BLUE, fname))) | |
d29fe6e2 SG |
156 | if (len(result.problems) != result.errors + result.warnings + |
157 | result.checks): | |
a920a17b | 158 | print("Internal error: some problems lost") |
d29fe6e2 | 159 | for item in result.problems: |
8aa41363 SG |
160 | sys.stderr.write( |
161 | GetWarningMsg(col, item.get('type', '<unknown>'), | |
afb9bf55 | 162 | item.get('file', '<unknown>'), |
a920a17b | 163 | item.get('line', 0), item.get('msg', 'message'))) |
d29fe6e2 | 164 | |
a920a17b | 165 | #print(stdout) |
d29fe6e2 SG |
166 | if error_count or warning_count or check_count: |
167 | str = 'checkpatch.pl found %d error(s), %d warning(s), %d checks(s)' | |
0d24de9d SG |
168 | color = col.GREEN |
169 | if warning_count: | |
170 | color = col.YELLOW | |
171 | if error_count: | |
172 | color = col.RED | |
a920a17b | 173 | print(col.Color(color, str % (error_count, warning_count, check_count))) |
0d24de9d SG |
174 | return False |
175 | return True |