humotion / stylecheck / cpplint-wrapper.py @ 8d293778
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() |