Statistics
| Branch: | Tag: | Revision:

humotion / stylecheck / cpplint-wrapper.py @ 25400c71

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()