humotion / stylecheck / cpplint-wrapper.py @ 6a2d467f
History | View | Annotate | Download (7.864 KB)
1 |
#!/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() |