blob: 8a6ff2af13423eefad75f1f31b34ed09c15c4cab [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):
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000239 return ['src', 'include', 'samples', join('test', 'cctest')]
240
241 def GetCpplintScript(self, prio_path):
242 for path in [prio_path] + os.environ["PATH"].split(os.pathsep):
243 path = path.strip('"')
244 cpplint = os.path.join(path, "cpplint.py")
245 if os.path.isfile(cpplint):
246 return cpplint
247
248 return None
Steve Blocka7e24c12009-10-30 11:49:00 +0000249
250 def ProcessFiles(self, files, path):
Steve Blockd0582a62009-12-15 09:54:21 +0000251 good_files_cache = FileContentsCache('.cpplint-cache')
252 good_files_cache.Load()
253 files = good_files_cache.FilterUnchangedFiles(files)
254 if len(files) == 0:
255 print 'No changes in files detected. Skipping cpplint check.'
256 return True
257
Steve Blocka7e24c12009-10-30 11:49:00 +0000258 filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000259 command = [sys.executable, 'cpplint.py', '--filter', filt]
260 cpplint = self.GetCpplintScript(join(path, "tools"))
261 if cpplint is None:
262 print('Could not find cpplint.py. Make sure '
263 'depot_tools is installed and in the path.')
264 sys.exit(1)
265
266 command = [sys.executable, cpplint, '--filter', filt]
Steve Blockd0582a62009-12-15 09:54:21 +0000267
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100268 commands = join([command + [file] for file in files])
269 count = multiprocessing.cpu_count()
270 pool = multiprocessing.Pool(count)
271 try:
272 results = pool.map_async(CppLintWorker, commands).get(999999)
273 except KeyboardInterrupt:
274 print "\nCaught KeyboardInterrupt, terminating workers."
275 sys.exit(1)
Steve Blockd0582a62009-12-15 09:54:21 +0000276
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100277 for i in range(len(files)):
278 if results[i] > 0:
279 good_files_cache.RemoveFile(files[i])
280
281 total_errors = sum(results)
282 print "Total errors found: %d" % total_errors
Steve Blockd0582a62009-12-15 09:54:21 +0000283 good_files_cache.Save()
Ben Murdoch3ef787d2012-04-12 10:51:47 +0100284 return total_errors == 0
Steve Blocka7e24c12009-10-30 11:49:00 +0000285
286
287COPYRIGHT_HEADER_PATTERN = re.compile(
Leon Clarkee46be812010-01-19 14:06:41 +0000288 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
Steve Blocka7e24c12009-10-30 11:49:00 +0000289
290class SourceProcessor(SourceFileProcessor):
291 """
Ben Murdoch589d6972011-11-30 16:04:58 +0000292 Check that all files include a copyright notice and no trailing whitespaces.
Steve Blocka7e24c12009-10-30 11:49:00 +0000293 """
294
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000295 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
296 '.status', '.gyp', '.gypi']
Ben Murdoch589d6972011-11-30 16:04:58 +0000297
298 # Overwriting the one in the parent class.
299 def FindFilesIn(self, path):
300 if os.path.exists(path+'/.git'):
301 output = subprocess.Popen('git ls-files --full-name',
302 stdout=PIPE, cwd=path, shell=True)
303 result = []
304 for file in output.stdout.read().split():
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000305 for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
Ben Murdoch589d6972011-11-30 16:04:58 +0000306 if self.IgnoreDir(dir_part):
307 break
308 else:
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000309 if (self.IsRelevant(file) and os.path.exists(file)
310 and not self.IgnoreFile(file)):
Ben Murdoch589d6972011-11-30 16:04:58 +0000311 result.append(join(path, file))
312 if output.wait() == 0:
313 return result
314 return super(SourceProcessor, self).FindFilesIn(path)
315
Steve Blocka7e24c12009-10-30 11:49:00 +0000316 def IsRelevant(self, name):
317 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
318 if name.endswith(ext):
319 return True
320 return False
321
322 def GetPathsToSearch(self):
323 return ['.']
324
325 def IgnoreDir(self, name):
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000326 return (super(SourceProcessor, self).IgnoreDir(name) or
327 name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
Steve Blocka7e24c12009-10-30 11:49:00 +0000328
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000329 IGNORE_COPYRIGHTS = ['cpplint.py',
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000330 'daemon.py',
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000331 'earley-boyer.js',
332 'raytrace.js',
333 'crypto.js',
334 'libraries.cc',
335 'libraries-empty.cc',
336 'jsmin.py',
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000337 'regexp-pcre.js',
338 'gnuplot-4.6.3-emscripten.js']
Ben Murdoch69a99ed2011-11-30 16:03:39 +0000339 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
Steve Blocka7e24c12009-10-30 11:49:00 +0000340
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000341 def EndOfDeclaration(self, line):
342 return line == "}" or line == "};"
343
344 def StartOfDeclaration(self, line):
345 return line.find("//") == 0 or \
346 line.find("/*") == 0 or \
347 line.find(") {") != -1
348
Steve Blocka7e24c12009-10-30 11:49:00 +0000349 def ProcessContents(self, name, contents):
350 result = True
351 base = basename(name)
352 if not base in SourceProcessor.IGNORE_TABS:
353 if '\t' in contents:
354 print "%s contains tabs" % name
355 result = False
356 if not base in SourceProcessor.IGNORE_COPYRIGHTS:
357 if not COPYRIGHT_HEADER_PATTERN.search(contents):
358 print "%s is missing a correct copyright header." % name
359 result = False
Ben Murdoch589d6972011-11-30 16:04:58 +0000360 if ' \n' in contents or contents.endswith(' '):
361 line = 0
362 lines = []
363 parts = contents.split(' \n')
364 if not contents.endswith(' '):
365 parts.pop()
366 for part in parts:
367 line += part.count('\n') + 1
368 lines.append(str(line))
369 linenumbers = ', '.join(lines)
370 if len(lines) > 1:
371 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
372 else:
373 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
374 result = False
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000375 if not contents.endswith('\n') or contents.endswith('\n\n'):
376 print "%s does not end with a single new line." % name
377 result = False
378 # Check two empty lines between declarations.
379 if name.endswith(".cc"):
380 line = 0
381 lines = []
382 parts = contents.split('\n')
383 while line < len(parts) - 2:
384 if self.EndOfDeclaration(parts[line]):
385 if self.StartOfDeclaration(parts[line + 1]):
386 lines.append(str(line + 1))
387 line += 1
388 elif parts[line + 1] == "" and \
389 self.StartOfDeclaration(parts[line + 2]):
390 lines.append(str(line + 1))
391 line += 2
392 line += 1
393 if len(lines) >= 1:
394 linenumbers = ', '.join(lines)
395 if len(lines) > 1:
396 print "%s does not have two empty lines between declarations " \
397 "in lines %s." % (name, linenumbers)
398 else:
399 print "%s does not have two empty lines between declarations " \
400 "in line %s." % (name, linenumbers)
401 result = False
Steve Blocka7e24c12009-10-30 11:49:00 +0000402 return result
403
404 def ProcessFiles(self, files, path):
405 success = True
Ben Murdoch589d6972011-11-30 16:04:58 +0000406 violations = 0
Steve Blocka7e24c12009-10-30 11:49:00 +0000407 for file in files:
408 try:
409 handle = open(file)
410 contents = handle.read()
Ben Murdoch589d6972011-11-30 16:04:58 +0000411 if not self.ProcessContents(file, contents):
412 success = False
413 violations += 1
Steve Blocka7e24c12009-10-30 11:49:00 +0000414 finally:
415 handle.close()
Ben Murdoch589d6972011-11-30 16:04:58 +0000416 print "Total violating files: %s" % violations
Steve Blocka7e24c12009-10-30 11:49:00 +0000417 return success
418
419
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000420def CheckRuntimeVsNativesNameClashes(workspace):
421 code = subprocess.call(
422 [sys.executable, join(workspace, "tools", "check-name-clashes.py")])
423 return code == 0
424
425
426def CheckExternalReferenceRegistration(workspace):
427 code = subprocess.call(
428 [sys.executable, join(workspace, "tools", "external-reference-check.py")])
429 return code == 0
430
431
Steve Blocka7e24c12009-10-30 11:49:00 +0000432def GetOptions():
433 result = optparse.OptionParser()
434 result.add_option('--no-lint', help="Do not run cpplint", default=False,
435 action="store_true")
436 return result
437
438
439def Main():
440 workspace = abspath(join(dirname(sys.argv[0]), '..'))
441 parser = GetOptions()
442 (options, args) = parser.parse_args()
443 success = True
Ben Murdoch589d6972011-11-30 16:04:58 +0000444 print "Running C++ lint check..."
Steve Blocka7e24c12009-10-30 11:49:00 +0000445 if not options.no_lint:
446 success = CppLintProcessor().Run(workspace) and success
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000447 print "Running copyright header, trailing whitespaces and " \
448 "two empty lines between declarations check..."
Steve Blocka7e24c12009-10-30 11:49:00 +0000449 success = SourceProcessor().Run(workspace) and success
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000450 success = CheckRuntimeVsNativesNameClashes(workspace) and success
451 success = CheckExternalReferenceRegistration(workspace) and success
Steve Blocka7e24c12009-10-30 11:49:00 +0000452 if success:
453 return 0
454 else:
455 return 1
456
457
458if __name__ == '__main__':
459 sys.exit(Main())