blob: 321d2910d566fed889c40cffad0075b757a2e909 [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
Ben Murdochb8a8cc12014-11-26 15:28:44 +000057build/include_alpha
Steve Blocka7e24c12009-10-30 11:49:00 +000058build/include_order
59build/printf_format
60build/storage_class
61legal/copyright
62readability/boost
63readability/braces
64readability/casting
Steve Blocka7e24c12009-10-30 11:49:00 +000065readability/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
Steve Blocka7e24c12009-10-30 11:49:00 +000083runtime/rtti
84runtime/sizeof
85runtime/string
86runtime/virtual
87runtime/vlog
88whitespace/blank_line
89whitespace/braces
90whitespace/comma
91whitespace/comments
Steve Blocka7e24c12009-10-30 11:49:00 +000092whitespace/ending_newline
93whitespace/indent
94whitespace/labels
95whitespace/line_length
96whitespace/newline
97whitespace/operators
98whitespace/parens
99whitespace/tab
100whitespace/todo
101""".split()
102
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000103# TODO(bmeurer): Fix and re-enable readability/check
Steve Blocka7e24c12009-10-30 11:49:00 +0000104
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:
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000117 if error_count == -1:
118 print "Failed to process %s" % command.pop()
119 return 1
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100120 break
121 m = LINT_OUTPUT_PATTERN.match(out_line)
122 if m:
123 out_lines += out_line
124 error_count += 1
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000125 sys.stdout.write(out_lines)
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100126 return error_count
127 except KeyboardInterrupt:
128 process.kill()
129 except:
130 print('Error running cpplint.py. Please make sure you have depot_tools' +
131 ' in your $PATH. Lint check skipped.')
132 process.kill()
133
134
Steve Blockd0582a62009-12-15 09:54:21 +0000135class FileContentsCache(object):
136
137 def __init__(self, sums_file_name):
138 self.sums = {}
139 self.sums_file_name = sums_file_name
140
141 def Load(self):
142 try:
143 sums_file = None
144 try:
145 sums_file = open(self.sums_file_name, 'r')
146 self.sums = pickle.load(sums_file)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000147 except:
148 # Cannot parse pickle for any reason. Not much we can do about it.
Steve Blockd0582a62009-12-15 09:54:21 +0000149 pass
150 finally:
151 if sums_file:
152 sums_file.close()
153
154 def Save(self):
155 try:
156 sums_file = open(self.sums_file_name, 'w')
157 pickle.dump(self.sums, sums_file)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000158 except:
159 # Failed to write pickle. Try to clean-up behind us.
160 if sums_file:
161 sums_file.close()
162 try:
163 os.unlink(self.sums_file_name)
164 except:
165 pass
Steve Blockd0582a62009-12-15 09:54:21 +0000166 finally:
167 sums_file.close()
168
169 def FilterUnchangedFiles(self, files):
170 changed_or_new = []
171 for file in files:
172 try:
173 handle = open(file, "r")
Ben Murdochbb769b22010-08-11 14:56:33 +0100174 file_sum = md5er(handle.read()).digest()
Steve Blockd0582a62009-12-15 09:54:21 +0000175 if not file in self.sums or self.sums[file] != file_sum:
176 changed_or_new.append(file)
177 self.sums[file] = file_sum
178 finally:
179 handle.close()
180 return changed_or_new
181
182 def RemoveFile(self, file):
183 if file in self.sums:
184 self.sums.pop(file)
185
186
Steve Blocka7e24c12009-10-30 11:49:00 +0000187class SourceFileProcessor(object):
188 """
189 Utility class that can run through a directory structure, find all relevant
190 files and invoke a custom check on the files.
191 """
192
193 def Run(self, path):
194 all_files = []
195 for file in self.GetPathsToSearch():
196 all_files += self.FindFilesIn(join(path, file))
197 if not self.ProcessFiles(all_files, path):
198 return False
199 return True
200
201 def IgnoreDir(self, name):
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000202 return (name.startswith('.') or
203 name in ('buildtools', 'data', 'gmock', 'gtest', 'kraken',
204 'octane', 'sunspider'))
Steve Blocka7e24c12009-10-30 11:49:00 +0000205
206 def IgnoreFile(self, name):
207 return name.startswith('.')
208
209 def FindFilesIn(self, path):
210 result = []
211 for (root, dirs, files) in os.walk(path):
212 for ignored in [x for x in dirs if self.IgnoreDir(x)]:
213 dirs.remove(ignored)
214 for file in files:
215 if not self.IgnoreFile(file) and self.IsRelevant(file):
216 result.append(join(root, file))
217 return result
218
219
220class CppLintProcessor(SourceFileProcessor):
221 """
222 Lint files to check that they follow the google code style.
223 """
224
225 def IsRelevant(self, name):
226 return name.endswith('.cc') or name.endswith('.h')
227
228 def IgnoreDir(self, name):
229 return (super(CppLintProcessor, self).IgnoreDir(name)
230 or (name == 'third_party'))
231
232 IGNORE_LINT = ['flag-definitions.h']
Steve Blockd0582a62009-12-15 09:54:21 +0000233
Steve Blocka7e24c12009-10-30 11:49:00 +0000234 def IgnoreFile(self, name):
235 return (super(CppLintProcessor, self).IgnoreFile(name)
236 or (name in CppLintProcessor.IGNORE_LINT))
237
238 def GetPathsToSearch(self):
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400239 return ['src', 'include', 'samples', join('test', 'cctest'),
240 join('test', 'unittests')]
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000241
242 def GetCpplintScript(self, prio_path):
243 for path in [prio_path] + os.environ["PATH"].split(os.pathsep):
244 path = path.strip('"')
245 cpplint = os.path.join(path, "cpplint.py")
246 if os.path.isfile(cpplint):
247 return cpplint
248
249 return None
Steve Blocka7e24c12009-10-30 11:49:00 +0000250
251 def ProcessFiles(self, files, path):
Steve Blockd0582a62009-12-15 09:54:21 +0000252 good_files_cache = FileContentsCache('.cpplint-cache')
253 good_files_cache.Load()
254 files = good_files_cache.FilterUnchangedFiles(files)
255 if len(files) == 0:
256 print 'No changes in files detected. Skipping cpplint check.'
257 return True
258
Steve Blocka7e24c12009-10-30 11:49:00 +0000259 filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000260 command = [sys.executable, 'cpplint.py', '--filter', filt]
261 cpplint = self.GetCpplintScript(join(path, "tools"))
262 if cpplint is None:
263 print('Could not find cpplint.py. Make sure '
264 'depot_tools is installed and in the path.')
265 sys.exit(1)
266
267 command = [sys.executable, cpplint, '--filter', filt]
Steve Blockd0582a62009-12-15 09:54:21 +0000268
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100269 commands = join([command + [file] for file in files])
270 count = multiprocessing.cpu_count()
271 pool = multiprocessing.Pool(count)
272 try:
273 results = pool.map_async(CppLintWorker, commands).get(999999)
274 except KeyboardInterrupt:
275 print "\nCaught KeyboardInterrupt, terminating workers."
276 sys.exit(1)
Steve Blockd0582a62009-12-15 09:54:21 +0000277
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100278 for i in range(len(files)):
279 if results[i] > 0:
280 good_files_cache.RemoveFile(files[i])
281
282 total_errors = sum(results)
283 print "Total errors found: %d" % total_errors
Steve Blockd0582a62009-12-15 09:54:21 +0000284 good_files_cache.Save()
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100285 return total_errors == 0
Steve Blocka7e24c12009-10-30 11:49:00 +0000286
287
288COPYRIGHT_HEADER_PATTERN = re.compile(
Leon Clarkee46be812010-01-19 14:06:41 +0000289 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
Steve Blocka7e24c12009-10-30 11:49:00 +0000290
291class SourceProcessor(SourceFileProcessor):
292 """
Ben Murdoch589d6972011-11-30 16:04:58 +0000293 Check that all files include a copyright notice and no trailing whitespaces.
Steve Blocka7e24c12009-10-30 11:49:00 +0000294 """
295
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000296 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
297 '.status', '.gyp', '.gypi']
Ben Murdoch589d6972011-11-30 16:04:58 +0000298
299 # Overwriting the one in the parent class.
300 def FindFilesIn(self, path):
301 if os.path.exists(path+'/.git'):
302 output = subprocess.Popen('git ls-files --full-name',
303 stdout=PIPE, cwd=path, shell=True)
304 result = []
305 for file in output.stdout.read().split():
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000306 for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
Ben Murdoch589d6972011-11-30 16:04:58 +0000307 if self.IgnoreDir(dir_part):
308 break
309 else:
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000310 if (self.IsRelevant(file) and os.path.exists(file)
311 and not self.IgnoreFile(file)):
Ben Murdoch589d6972011-11-30 16:04:58 +0000312 result.append(join(path, file))
313 if output.wait() == 0:
314 return result
315 return super(SourceProcessor, self).FindFilesIn(path)
316
Steve Blocka7e24c12009-10-30 11:49:00 +0000317 def IsRelevant(self, name):
318 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
319 if name.endswith(ext):
320 return True
321 return False
322
323 def GetPathsToSearch(self):
324 return ['.']
325
326 def IgnoreDir(self, name):
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000327 return (super(SourceProcessor, self).IgnoreDir(name) or
328 name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
Steve Blocka7e24c12009-10-30 11:49:00 +0000329
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400330 IGNORE_COPYRIGHTS = ['box2d.js',
331 'cpplint.py',
332 'copy.js',
333 'corrections.js',
334 'crypto.js',
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000335 'daemon.py',
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000336 'earley-boyer.js',
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400337 'fannkuch.js',
338 'fasta.js',
339 'jsmin.py',
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000340 'libraries.cc',
341 'libraries-empty.cc',
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400342 'lua_binarytrees.js',
343 'memops.js',
344 'primes.js',
345 'raytrace.js',
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000346 'regexp-pcre.js',
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400347 'gnuplot-4.6.3-emscripten.js',
348 'zlib.js']
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000349 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
Steve Blocka7e24c12009-10-30 11:49:00 +0000350
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000351 def EndOfDeclaration(self, line):
352 return line == "}" or line == "};"
353
354 def StartOfDeclaration(self, line):
355 return line.find("//") == 0 or \
356 line.find("/*") == 0 or \
357 line.find(") {") != -1
358
Steve Blocka7e24c12009-10-30 11:49:00 +0000359 def ProcessContents(self, name, contents):
360 result = True
361 base = basename(name)
362 if not base in SourceProcessor.IGNORE_TABS:
363 if '\t' in contents:
364 print "%s contains tabs" % name
365 result = False
366 if not base in SourceProcessor.IGNORE_COPYRIGHTS:
367 if not COPYRIGHT_HEADER_PATTERN.search(contents):
368 print "%s is missing a correct copyright header." % name
369 result = False
Ben Murdoch589d6972011-11-30 16:04:58 +0000370 if ' \n' in contents or contents.endswith(' '):
371 line = 0
372 lines = []
373 parts = contents.split(' \n')
374 if not contents.endswith(' '):
375 parts.pop()
376 for part in parts:
377 line += part.count('\n') + 1
378 lines.append(str(line))
379 linenumbers = ', '.join(lines)
380 if len(lines) > 1:
381 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
382 else:
383 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
384 result = False
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000385 if not contents.endswith('\n') or contents.endswith('\n\n'):
386 print "%s does not end with a single new line." % name
387 result = False
388 # Check two empty lines between declarations.
389 if name.endswith(".cc"):
390 line = 0
391 lines = []
392 parts = contents.split('\n')
393 while line < len(parts) - 2:
394 if self.EndOfDeclaration(parts[line]):
395 if self.StartOfDeclaration(parts[line + 1]):
396 lines.append(str(line + 1))
397 line += 1
398 elif parts[line + 1] == "" and \
399 self.StartOfDeclaration(parts[line + 2]):
400 lines.append(str(line + 1))
401 line += 2
402 line += 1
403 if len(lines) >= 1:
404 linenumbers = ', '.join(lines)
405 if len(lines) > 1:
406 print "%s does not have two empty lines between declarations " \
407 "in lines %s." % (name, linenumbers)
408 else:
409 print "%s does not have two empty lines between declarations " \
410 "in line %s." % (name, linenumbers)
411 result = False
Steve Blocka7e24c12009-10-30 11:49:00 +0000412 return result
413
414 def ProcessFiles(self, files, path):
415 success = True
Ben Murdoch589d6972011-11-30 16:04:58 +0000416 violations = 0
Steve Blocka7e24c12009-10-30 11:49:00 +0000417 for file in files:
418 try:
419 handle = open(file)
420 contents = handle.read()
Ben Murdoch589d6972011-11-30 16:04:58 +0000421 if not self.ProcessContents(file, contents):
422 success = False
423 violations += 1
Steve Blocka7e24c12009-10-30 11:49:00 +0000424 finally:
425 handle.close()
Ben Murdoch589d6972011-11-30 16:04:58 +0000426 print "Total violating files: %s" % violations
Steve Blocka7e24c12009-10-30 11:49:00 +0000427 return success
428
429
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000430def CheckRuntimeVsNativesNameClashes(workspace):
431 code = subprocess.call(
432 [sys.executable, join(workspace, "tools", "check-name-clashes.py")])
433 return code == 0
434
435
436def CheckExternalReferenceRegistration(workspace):
437 code = subprocess.call(
438 [sys.executable, join(workspace, "tools", "external-reference-check.py")])
439 return code == 0
440
441
Steve Blocka7e24c12009-10-30 11:49:00 +0000442def GetOptions():
443 result = optparse.OptionParser()
444 result.add_option('--no-lint', help="Do not run cpplint", default=False,
445 action="store_true")
446 return result
447
448
449def Main():
450 workspace = abspath(join(dirname(sys.argv[0]), '..'))
451 parser = GetOptions()
452 (options, args) = parser.parse_args()
453 success = True
Ben Murdoch589d6972011-11-30 16:04:58 +0000454 print "Running C++ lint check..."
Steve Blocka7e24c12009-10-30 11:49:00 +0000455 if not options.no_lint:
456 success = CppLintProcessor().Run(workspace) and success
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000457 print "Running copyright header, trailing whitespaces and " \
458 "two empty lines between declarations check..."
Steve Blocka7e24c12009-10-30 11:49:00 +0000459 success = SourceProcessor().Run(workspace) and success
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000460 success = CheckRuntimeVsNativesNameClashes(workspace) and success
461 success = CheckExternalReferenceRegistration(workspace) and success
Steve Blocka7e24c12009-10-30 11:49:00 +0000462 if success:
463 return 0
464 else:
465 return 1
466
467
468if __name__ == '__main__':
469 sys.exit(Main())