humotion / stylecheck / cpplint-wrapper.py @ 6a2d467f
History | View | Annotate | Download (7.864 KB)
1 | 6a2d467f | Simon Schulz | #!/usr/bin/python
|
---|---|---|---|
2 | ## Copyright (c) 2014, Hewlett-Packard Development Company, LP.
|
||
3 | ## The license and distribution terms for this file are placed in LICENSE.txt.
|
||
4 | # Wraps Google's cpplint.py for a few additional features;
|
||
5 | # 1) Recursively look for files to check. cpplint.py might do this out of box in future.
|
||
6 | # 2) Control whether we return a non-zero exit code if there are style errros (--style-error arg).
|
||
7 | # 3) Skip checking a file that had no warnings previously and has not changed since then.
|
||
8 | import getopt |
||
9 | import os |
||
10 | import json |
||
11 | import re |
||
12 | import string |
||
13 | import subprocess |
||
14 | import sys |
||
15 | import time |
||
16 | import unicodedata |
||
17 | |||
18 | |||
19 | wrapper_usage = """
|
||
20 | Syntax: cpplint-wrapper.py
|
||
21 | [--style-error]
|
||
22 | [--excludes=<excluded folder name regex>]
|
||
23 | [--extensions=<file extension regex>]
|
||
24 | [--cpplint-file=<path of cpplint.py>]
|
||
25 | [--history-file=<path of history file that describes the result of previous run>]
|
||
26 | [other flags for cpplint, which are directly passed to cpplint]
|
||
27 | <file/folder> [path] ...
|
||
28 |
|
||
29 | Flags specific to cpplint-wrapper:
|
||
30 |
|
||
31 | style-error
|
||
32 | If specified, this python script returns a non-zero exit code if there are some
|
||
33 | style warnings (which is the cpplint's behavior). Default is false.
|
||
34 |
|
||
35 | excludes
|
||
36 | If specified, files/folders whose names meet this regex are skipped.
|
||
37 | Default is "(\.git|\.svn)".
|
||
38 |
|
||
39 | extensions
|
||
40 | If specified, files whose extensions meet this regex are verified by cpplint.
|
||
41 | Default is "\.(c|h|cc|cpp|hpp)$".
|
||
42 |
|
||
43 | cpplint-file
|
||
44 | If specified, the path of cpplint.py. Default is "cpplint.py" (in working folder).
|
||
45 |
|
||
46 | history-file
|
||
47 | If specified, the path of history file to skip files that had no changes.
|
||
48 | Default is ".lint_history" (in working folder)
|
||
49 |
|
||
50 | file/folder
|
||
51 | Required argument. The file(s) or folder(s) to recursively look for target files to
|
||
52 | run cpplint on.
|
||
53 | """
|
||
54 | |||
55 | style_error = False
|
||
56 | excludes = '(\.git|\.svn)'
|
||
57 | excludes_regex = re.compile(excludes); |
||
58 | extensions = '\.(c|h|cc|cpp|hpp)$'
|
||
59 | extensions_regex = re.compile(extensions); |
||
60 | cpplint_file = 'cpplint.py'
|
||
61 | history_file = '.lint_history'
|
||
62 | |||
63 | def print_usage(wrong_parameter): |
||
64 | sys.stderr.write(wrapper_usage) |
||
65 | if wrong_parameter:
|
||
66 | sys.exit('\Wrong Parameter: ' + wrong_parameter)
|
||
67 | else:
|
||
68 | sys.exit(1)
|
||
69 | |||
70 | def parse_arguments(args): |
||
71 | (opts, names) = getopt.getopt(args, '', ['help', |
||
72 | 'style-error', 'excludes=', 'extensions=', |
||
73 | 'cpplint-file=', 'history-file=', |
||
74 | # followings are for cpplint
|
||
75 | 'output=', 'verbose=', 'counting=', 'filter=', |
||
76 | 'root=', 'linelength=']) |
||
77 | cpplint_arguments = [] |
||
78 | for (opt, val) in opts: |
||
79 | if opt == '--help': |
||
80 | print_usage(None)
|
||
81 | elif opt == '--style-error': |
||
82 | global style_error
|
||
83 | style_error = True
|
||
84 | elif opt == '--excludes': |
||
85 | global excludes
|
||
86 | global excludes_regex
|
||
87 | excludes = val |
||
88 | excludes_regex = re.compile(excludes); |
||
89 | elif opt == '--extensions': |
||
90 | global extensions
|
||
91 | global extensions_regex
|
||
92 | extensions = val |
||
93 | extensions_regex = re.compile(extensions); |
||
94 | elif opt == '--cpplint-file': |
||
95 | global cpplint_file
|
||
96 | cpplint_file = val |
||
97 | elif opt == '--history-file': |
||
98 | global history_file
|
||
99 | history_file = val |
||
100 | else:
|
||
101 | cpplint_arguments.append(opt + '=' + val)
|
||
102 | |||
103 | if not names: |
||
104 | print_usage('No files/folders were specified.')
|
||
105 | |||
106 | return (names, cpplint_arguments)
|
||
107 | |||
108 | def get_files_recursive(folder, out_files): |
||
109 | for name in os.listdir(folder): |
||
110 | if excludes_regex.search(name) is None: |
||
111 | path = os.path.join(folder, name) |
||
112 | if os.path.isfile(path):
|
||
113 | if extensions_regex.search(name) is not None: |
||
114 | out_files.append(path) |
||
115 | else:
|
||
116 | get_files_recursive(path, out_files) |
||
117 | |||
118 | def compute_dir_index(files): |
||
119 | """ Return a tuple containing a dictionary: filepath => last
|
||
120 | """
|
||
121 | index = {} |
||
122 | start = time.time() |
||
123 | for f in files: |
||
124 | index[f] = str(os.path.getmtime(f))
|
||
125 | end = time.time() |
||
126 | sys.stdout.write('cpplint-wrapper: Checked timestamp of ' + str(len(files)) + ' files in ' |
||
127 | + str(end - start) + ' sec.\n') |
||
128 | |||
129 | return index
|
||
130 | |||
131 | def store_dir_index(index, path): |
||
132 | start = time.time() |
||
133 | f = open(path, 'w') |
||
134 | json.dump(index, f) |
||
135 | f.close() |
||
136 | end = time.time() |
||
137 | sys.stdout.write('cpplint-wrapper: Wrote ' + path + ' (' + str(len(index)) + ' entries) in ' + str(end - start) + ' sec.\n') |
||
138 | |||
139 | def load_dir_index(path): |
||
140 | start = time.time() |
||
141 | f = open(path, 'r') |
||
142 | idx = json.load(f) |
||
143 | f.close() |
||
144 | end = time.time() |
||
145 | sys.stdout.write('cpplint-wrapper: Read ' + path + ' (' + str(len(idx)) + ' entries) in ' + str(end - start) + ' sec.\n') |
||
146 | return idx
|
||
147 | |||
148 | def compute_diff(files, index_base, index_now): |
||
149 | new_index = {} |
||
150 | |||
151 | for f in files: |
||
152 | if not f in index_base or index_base[f] != index_now[f]: |
||
153 | new_index[f] = index_now[f] |
||
154 | return new_index
|
||
155 | |||
156 | def exec_cpplint(files, cpplint_arguments): |
||
157 | if not files: |
||
158 | sys.stdout.write('No files to check\n')
|
||
159 | return False |
||
160 | |||
161 | # run cpplint only for files that have been changed or had some warning previously
|
||
162 | index_last = {} |
||
163 | if os.path.isfile(history_file):
|
||
164 | index_last = load_dir_index(history_file) |
||
165 | index_now = compute_dir_index(files) |
||
166 | index_now = compute_diff(files, index_last, index_now) |
||
167 | if not index_now: |
||
168 | sys.stdout.write('cpplint-wrapper: No files have been changed\n')
|
||
169 | return False |
||
170 | |||
171 | args = [sys.executable, cpplint_file] |
||
172 | args.append('--extensions=c,cc,cpp,h,hpp,cu,cuh') # cpplint's default extensions lack "hpp" |
||
173 | args += cpplint_arguments |
||
174 | for f in index_now: |
||
175 | args.append(f) |
||
176 | |||
177 | sys.stdout.write('Launching cpplint for ' + str(len(index_now)) + ' files.\n') |
||
178 | # sys.stdout.write('arguments: ' + ' '.join(args) + '\n')
|
||
179 | # sys.stdout.write('Launching cpplint (' + cpplint_file + ') for ' + str(len(files))
|
||
180 | # + ' files. arguments: ' + ' '.join(args) + '\n')
|
||
181 | proc = subprocess.Popen(args, bufsize=65536, stderr=subprocess.PIPE, close_fds=True) |
||
182 | proc.wait() |
||
183 | has_error = False
|
||
184 | clean_index = {} |
||
185 | clean_index.update(index_last) |
||
186 | clean_index.update(index_now) |
||
187 | for line in proc.stderr: |
||
188 | # This is annoying. Why cpplint writes this to _stderr_??
|
||
189 | if not line.startswith("Done processing "): |
||
190 | has_error = True
|
||
191 | ## eg.
|
||
192 | ## /home/kimurhid/projects/foedus_code/foedus-core/include/foedus/cache/cache_hashtable.hpp:205: Namespace should be terminated with "// namespace cache" [readability/namespace] [5]
|
||
193 | ## We parse the line and remember the file to be excluded from the "clean" list
|
||
194 | if line.find(':') > 0: |
||
195 | f = line[:line.find(':')]
|
||
196 | if f in clean_index: |
||
197 | del clean_index[f]
|
||
198 | if sys.stderr.isatty():
|
||
199 | sys.stderr.write('\033[93m' + line + '\033[0m') # Put a color that stands out |
||
200 | else:
|
||
201 | sys.stderr.write(line) # If the client (kdevelop?) doesn't support, avoid it.
|
||
202 | |||
203 | # store the clean list to speed up next execution
|
||
204 | store_dir_index(clean_index, history_file) |
||
205 | |||
206 | return has_error
|
||
207 | |||
208 | def main(): |
||
209 | (names, cpplint_arguments) = parse_arguments(sys.argv[1:])
|
||
210 | files = [] |
||
211 | for name in names: |
||
212 | if os.path.isfile(name):
|
||
213 | files.append(name) |
||
214 | else:
|
||
215 | get_files_recursive(name, files) |
||
216 | |||
217 | has_error = exec_cpplint(files, cpplint_arguments) |
||
218 | if has_error and style_error: |
||
219 | sys.stderr.write('There was cpplint error(s) and --style-error was specified. non-zero exit code.\n')
|
||
220 | sys.exit(1)
|
||
221 | else:
|
||
222 | sys.exit(0)
|
||
223 | |||
224 | if __name__ == '__main__': |
||
225 | main() |