blob: a5f4c614d04a800c816991bdc6a00f27101aa5f1 [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001#!/usr/bin/env python
2#
Ben Murdoch3ef787d2012-04-12 10:51:47 +01003# Copyright 2012 the V8 project authors. All rights reserved.
Steve Blocka7e24c12009-10-30 11:49:00 +00004# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8# * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10# * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following
12# disclaimer in the documentation and/or other materials provided
13# with the distribution.
14# * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived
16# from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
Ben Murdochbb769b22010-08-11 14:56:33 +010030try:
31 import hashlib
32 md5er = hashlib.md5
33except ImportError, e:
34 import md5
35 md5er = md5.new
Steve Blocka7e24c12009-10-30 11:49:00 +000036
Ben Murdochbb769b22010-08-11 14:56:33 +010037
Steve Blocka7e24c12009-10-30 11:49:00 +000038import optparse
39import os
40from os.path import abspath, join, dirname, basename, exists
Steve Blockd0582a62009-12-15 09:54:21 +000041import pickle
Steve Blocka7e24c12009-10-30 11:49:00 +000042import re
43import sys
44import subprocess
Ben Murdoch3ef787d2012-04-12 10:51:47 +010045import multiprocessing
Ben Murdoch589d6972011-11-30 16:04:58 +000046from subprocess import PIPE
Steve Blocka7e24c12009-10-30 11:49:00 +000047
48# Disabled LINT rules and reason.
49# build/include_what_you_use: Started giving false positives for variables
50# named "string" and "map" assuming that you needed to include STL headers.
51
52ENABLED_LINT_RULES = """
53build/class
54build/deprecated
55build/endif_comment
56build/forward_decl
57build/include_order
58build/printf_format
59build/storage_class
60legal/copyright
61readability/boost
62readability/braces
63readability/casting
64readability/check
65readability/constructors
66readability/fn_size
67readability/function
68readability/multiline_comment
69readability/multiline_string
70readability/streams
71readability/todo
72readability/utf8
73runtime/arrays
74runtime/casting
75runtime/deprecated_fn
76runtime/explicit
77runtime/int
78runtime/memset
79runtime/mutex
80runtime/nonconf
81runtime/printf
82runtime/printf_format
83runtime/references
84runtime/rtti
85runtime/sizeof
86runtime/string
87runtime/virtual
88runtime/vlog
89whitespace/blank_line
90whitespace/braces
91whitespace/comma
92whitespace/comments
Steve Blocka7e24c12009-10-30 11:49:00 +000093whitespace/ending_newline
94whitespace/indent
95whitespace/labels
96whitespace/line_length
97whitespace/newline
98whitespace/operators
99whitespace/parens
100whitespace/tab
101whitespace/todo
102""".split()
103
104
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100105LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
106
107
108def CppLintWorker(command):
109 try:
110 process = subprocess.Popen(command, stderr=subprocess.PIPE)
111 process.wait()
112 out_lines = ""
113 error_count = -1
114 while True:
115 out_line = process.stderr.readline()
116 if out_line == '' and process.poll() != None:
117 break
118 m = LINT_OUTPUT_PATTERN.match(out_line)
119 if m:
120 out_lines += out_line
121 error_count += 1
122 sys.stderr.write(out_lines)
123 return error_count
124 except KeyboardInterrupt:
125 process.kill()
126 except:
127 print('Error running cpplint.py. Please make sure you have depot_tools' +
128 ' in your $PATH. Lint check skipped.')
129 process.kill()
130
131
Steve Blockd0582a62009-12-15 09:54:21 +0000132class FileContentsCache(object):
133
134 def __init__(self, sums_file_name):
135 self.sums = {}
136 self.sums_file_name = sums_file_name
137
138 def Load(self):
139 try:
140 sums_file = None
141 try:
142 sums_file = open(self.sums_file_name, 'r')
143 self.sums = pickle.load(sums_file)
144 except IOError:
145 # File might not exist, this is OK.
146 pass
147 finally:
148 if sums_file:
149 sums_file.close()
150
151 def Save(self):
152 try:
153 sums_file = open(self.sums_file_name, 'w')
154 pickle.dump(self.sums, sums_file)
155 finally:
156 sums_file.close()
157
158 def FilterUnchangedFiles(self, files):
159 changed_or_new = []
160 for file in files:
161 try:
162 handle = open(file, "r")
Ben Murdochbb769b22010-08-11 14:56:33 +0100163 file_sum = md5er(handle.read()).digest()
Steve Blockd0582a62009-12-15 09:54:21 +0000164 if not file in self.sums or self.sums[file] != file_sum:
165 changed_or_new.append(file)
166 self.sums[file] = file_sum
167 finally:
168 handle.close()
169 return changed_or_new
170
171 def RemoveFile(self, file):
172 if file in self.sums:
173 self.sums.pop(file)
174
175
Steve Blocka7e24c12009-10-30 11:49:00 +0000176class SourceFileProcessor(object):
177 """
178 Utility class that can run through a directory structure, find all relevant
179 files and invoke a custom check on the files.
180 """
181
182 def Run(self, path):
183 all_files = []
184 for file in self.GetPathsToSearch():
185 all_files += self.FindFilesIn(join(path, file))
186 if not self.ProcessFiles(all_files, path):
187 return False
188 return True
189
190 def IgnoreDir(self, name):
Steve Blockd0582a62009-12-15 09:54:21 +0000191 return name.startswith('.') or name == 'data' or name == 'sputniktests'
Steve Blocka7e24c12009-10-30 11:49:00 +0000192
193 def IgnoreFile(self, name):
194 return name.startswith('.')
195
196 def FindFilesIn(self, path):
197 result = []
198 for (root, dirs, files) in os.walk(path):
199 for ignored in [x for x in dirs if self.IgnoreDir(x)]:
200 dirs.remove(ignored)
201 for file in files:
202 if not self.IgnoreFile(file) and self.IsRelevant(file):
203 result.append(join(root, file))
204 return result
205
206
207class CppLintProcessor(SourceFileProcessor):
208 """
209 Lint files to check that they follow the google code style.
210 """
211
212 def IsRelevant(self, name):
213 return name.endswith('.cc') or name.endswith('.h')
214
215 def IgnoreDir(self, name):
216 return (super(CppLintProcessor, self).IgnoreDir(name)
217 or (name == 'third_party'))
218
219 IGNORE_LINT = ['flag-definitions.h']
Steve Blockd0582a62009-12-15 09:54:21 +0000220
Steve Blocka7e24c12009-10-30 11:49:00 +0000221 def IgnoreFile(self, name):
222 return (super(CppLintProcessor, self).IgnoreFile(name)
223 or (name in CppLintProcessor.IGNORE_LINT))
224
225 def GetPathsToSearch(self):
Shimeng (Simon) Wang8a31eba2010-12-06 19:01:33 -0800226 return ['src', 'preparser', 'include', 'samples', join('test', 'cctest')]
Steve Blocka7e24c12009-10-30 11:49:00 +0000227
228 def ProcessFiles(self, files, path):
Steve Blockd0582a62009-12-15 09:54:21 +0000229 good_files_cache = FileContentsCache('.cpplint-cache')
230 good_files_cache.Load()
231 files = good_files_cache.FilterUnchangedFiles(files)
232 if len(files) == 0:
233 print 'No changes in files detected. Skipping cpplint check.'
234 return True
235
Steve Blocka7e24c12009-10-30 11:49:00 +0000236 filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100237 command = ['cpplint.py', '--filter', filt]
Steve Blocka7e24c12009-10-30 11:49:00 +0000238 local_cpplint = join(path, "tools", "cpplint.py")
239 if exists(local_cpplint):
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100240 command = ['python', local_cpplint, '--filter', filt]
Steve Blockd0582a62009-12-15 09:54:21 +0000241
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100242 commands = join([command + [file] for file in files])
243 count = multiprocessing.cpu_count()
244 pool = multiprocessing.Pool(count)
245 try:
246 results = pool.map_async(CppLintWorker, commands).get(999999)
247 except KeyboardInterrupt:
248 print "\nCaught KeyboardInterrupt, terminating workers."
249 sys.exit(1)
Steve Blockd0582a62009-12-15 09:54:21 +0000250
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100251 for i in range(len(files)):
252 if results[i] > 0:
253 good_files_cache.RemoveFile(files[i])
254
255 total_errors = sum(results)
256 print "Total errors found: %d" % total_errors
Steve Blockd0582a62009-12-15 09:54:21 +0000257 good_files_cache.Save()
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100258 return total_errors == 0
Steve Blocka7e24c12009-10-30 11:49:00 +0000259
260
261COPYRIGHT_HEADER_PATTERN = re.compile(
Leon Clarkee46be812010-01-19 14:06:41 +0000262 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
Steve Blocka7e24c12009-10-30 11:49:00 +0000263
264class SourceProcessor(SourceFileProcessor):
265 """
Ben Murdoch589d6972011-11-30 16:04:58 +0000266 Check that all files include a copyright notice and no trailing whitespaces.
Steve Blocka7e24c12009-10-30 11:49:00 +0000267 """
268
269 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c', 'SConscript',
Ben Murdoch589d6972011-11-30 16:04:58 +0000270 'SConstruct', '.status', '.gyp', '.gypi']
271
272 # Overwriting the one in the parent class.
273 def FindFilesIn(self, path):
274 if os.path.exists(path+'/.git'):
275 output = subprocess.Popen('git ls-files --full-name',
276 stdout=PIPE, cwd=path, shell=True)
277 result = []
278 for file in output.stdout.read().split():
279 for dir_part in os.path.dirname(file).split(os.sep):
280 if self.IgnoreDir(dir_part):
281 break
282 else:
283 if self.IsRelevant(file) and not self.IgnoreFile(file):
284 result.append(join(path, file))
285 if output.wait() == 0:
286 return result
287 return super(SourceProcessor, self).FindFilesIn(path)
288
Steve Blocka7e24c12009-10-30 11:49:00 +0000289 def IsRelevant(self, name):
290 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
291 if name.endswith(ext):
292 return True
293 return False
294
295 def GetPathsToSearch(self):
296 return ['.']
297
298 def IgnoreDir(self, name):
299 return (super(SourceProcessor, self).IgnoreDir(name)
300 or (name == 'third_party')
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000301 or (name == 'gyp')
302 or (name == 'out')
Steve Blocka7e24c12009-10-30 11:49:00 +0000303 or (name == 'obj'))
304
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000305 IGNORE_COPYRIGHTS = ['cpplint.py',
306 'earley-boyer.js',
307 'raytrace.js',
308 'crypto.js',
309 'libraries.cc',
310 'libraries-empty.cc',
311 'jsmin.py',
312 'regexp-pcre.js']
313 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
Steve Blocka7e24c12009-10-30 11:49:00 +0000314
315 def ProcessContents(self, name, contents):
316 result = True
317 base = basename(name)
318 if not base in SourceProcessor.IGNORE_TABS:
319 if '\t' in contents:
320 print "%s contains tabs" % name
321 result = False
322 if not base in SourceProcessor.IGNORE_COPYRIGHTS:
323 if not COPYRIGHT_HEADER_PATTERN.search(contents):
324 print "%s is missing a correct copyright header." % name
325 result = False
Ben Murdoch589d6972011-11-30 16:04:58 +0000326 ext = base.split('.').pop()
327 if ' \n' in contents or contents.endswith(' '):
328 line = 0
329 lines = []
330 parts = contents.split(' \n')
331 if not contents.endswith(' '):
332 parts.pop()
333 for part in parts:
334 line += part.count('\n') + 1
335 lines.append(str(line))
336 linenumbers = ', '.join(lines)
337 if len(lines) > 1:
338 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
339 else:
340 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
341 result = False
Steve Blocka7e24c12009-10-30 11:49:00 +0000342 return result
343
344 def ProcessFiles(self, files, path):
345 success = True
Ben Murdoch589d6972011-11-30 16:04:58 +0000346 violations = 0
Steve Blocka7e24c12009-10-30 11:49:00 +0000347 for file in files:
348 try:
349 handle = open(file)
350 contents = handle.read()
Ben Murdoch589d6972011-11-30 16:04:58 +0000351 if not self.ProcessContents(file, contents):
352 success = False
353 violations += 1
Steve Blocka7e24c12009-10-30 11:49:00 +0000354 finally:
355 handle.close()
Ben Murdoch589d6972011-11-30 16:04:58 +0000356 print "Total violating files: %s" % violations
Steve Blocka7e24c12009-10-30 11:49:00 +0000357 return success
358
359
360def GetOptions():
361 result = optparse.OptionParser()
362 result.add_option('--no-lint', help="Do not run cpplint", default=False,
363 action="store_true")
364 return result
365
366
367def Main():
368 workspace = abspath(join(dirname(sys.argv[0]), '..'))
369 parser = GetOptions()
370 (options, args) = parser.parse_args()
371 success = True
Ben Murdoch589d6972011-11-30 16:04:58 +0000372 print "Running C++ lint check..."
Steve Blocka7e24c12009-10-30 11:49:00 +0000373 if not options.no_lint:
374 success = CppLintProcessor().Run(workspace) and success
Ben Murdoch589d6972011-11-30 16:04:58 +0000375 print "Running copyright header and trailing whitespaces check..."
Steve Blocka7e24c12009-10-30 11:49:00 +0000376 success = SourceProcessor().Run(workspace) and success
377 if success:
378 return 0
379 else:
380 return 1
381
382
383if __name__ == '__main__':
384 sys.exit(Main())