3 # Copyright 2012 the V8 project authors. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
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.
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.
33 except ImportError, e:
40 from os.path import abspath, join, dirname, basename, exists
45 import multiprocessing
46 from subprocess import PIPE
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.
52 ENABLED_LINT_RULES = """
65 readability/constructors
68 readability/multiline_comment
69 readability/multiline_string
92 whitespace/ending_newline
95 whitespace/line_length
103 # TODO(bmeurer): Fix and re-enable readability/check
105 LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
108 def CppLintWorker(command):
110 process = subprocess.Popen(command, stderr=subprocess.PIPE)
115 out_line = process.stderr.readline()
116 if out_line == '' and process.poll() != None:
117 if error_count == -1:
118 print "Failed to process %s" % command.pop()
121 m = LINT_OUTPUT_PATTERN.match(out_line)
123 out_lines += out_line
125 sys.stdout.write(out_lines)
127 except KeyboardInterrupt:
130 print('Error running cpplint.py. Please make sure you have depot_tools' +
131 ' in your $PATH. Lint check skipped.')
135 class FileContentsCache(object):
137 def __init__(self, sums_file_name):
139 self.sums_file_name = sums_file_name
145 sums_file = open(self.sums_file_name, 'r')
146 self.sums = pickle.load(sums_file)
148 # Cannot parse pickle for any reason. Not much we can do about it.
156 sums_file = open(self.sums_file_name, 'w')
157 pickle.dump(self.sums, sums_file)
159 # Failed to write pickle. Try to clean-up behind us.
163 os.unlink(self.sums_file_name)
169 def FilterUnchangedFiles(self, files):
173 handle = open(file, "r")
174 file_sum = md5er(handle.read()).digest()
175 if not file in self.sums or self.sums[file] != file_sum:
176 changed_or_new.append(file)
177 self.sums[file] = file_sum
180 return changed_or_new
182 def RemoveFile(self, file):
183 if file in self.sums:
187 class SourceFileProcessor(object):
189 Utility class that can run through a directory structure, find all relevant
190 files and invoke a custom check on the files.
195 for file in self.GetPathsToSearch():
196 all_files += self.FindFilesIn(join(path, file))
197 if not self.ProcessFiles(all_files, path):
201 def IgnoreDir(self, name):
202 return (name.startswith('.') or
203 name in ('buildtools', 'data', 'gmock', 'gtest', 'kraken',
204 'octane', 'sunspider'))
206 def IgnoreFile(self, name):
207 return name.startswith('.')
209 def FindFilesIn(self, path):
211 for (root, dirs, files) in os.walk(path):
212 for ignored in [x for x in dirs if self.IgnoreDir(x)]:
215 if not self.IgnoreFile(file) and self.IsRelevant(file):
216 result.append(join(root, file))
220 class CppLintProcessor(SourceFileProcessor):
222 Lint files to check that they follow the google code style.
225 def IsRelevant(self, name):
226 return name.endswith('.cc') or name.endswith('.h')
228 def IgnoreDir(self, name):
229 return (super(CppLintProcessor, self).IgnoreDir(name)
230 or (name == 'third_party'))
232 IGNORE_LINT = ['flag-definitions.h']
234 def IgnoreFile(self, name):
235 return (super(CppLintProcessor, self).IgnoreFile(name)
236 or (name in CppLintProcessor.IGNORE_LINT))
238 def GetPathsToSearch(self):
239 return ['src', 'include', 'samples', join('test', 'cctest')]
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):
250 def ProcessFiles(self, files, path):
251 good_files_cache = FileContentsCache('.cpplint-cache')
252 good_files_cache.Load()
253 files = good_files_cache.FilterUnchangedFiles(files)
255 print 'No changes in files detected. Skipping cpplint check.'
258 filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
259 command = [sys.executable, 'cpplint.py', '--filter', filt]
260 cpplint = self.GetCpplintScript(join(path, "tools"))
262 print('Could not find cpplint.py. Make sure '
263 'depot_tools is installed and in the path.')
266 command = [sys.executable, cpplint, '--filter', filt]
268 commands = join([command + [file] for file in files])
269 count = multiprocessing.cpu_count()
270 pool = multiprocessing.Pool(count)
272 results = pool.map_async(CppLintWorker, commands).get(999999)
273 except KeyboardInterrupt:
274 print "\nCaught KeyboardInterrupt, terminating workers."
277 for i in range(len(files)):
279 good_files_cache.RemoveFile(files[i])
281 total_errors = sum(results)
282 print "Total errors found: %d" % total_errors
283 good_files_cache.Save()
284 return total_errors == 0
287 COPYRIGHT_HEADER_PATTERN = re.compile(
288 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
290 class SourceProcessor(SourceFileProcessor):
292 Check that all files include a copyright notice and no trailing whitespaces.
295 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
296 '.status', '.gyp', '.gypi']
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)
304 for file in output.stdout.read().split():
305 for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
306 if self.IgnoreDir(dir_part):
309 if (self.IsRelevant(file) and os.path.exists(file)
310 and not self.IgnoreFile(file)):
311 result.append(join(path, file))
312 if output.wait() == 0:
314 return super(SourceProcessor, self).FindFilesIn(path)
316 def IsRelevant(self, name):
317 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
318 if name.endswith(ext):
322 def GetPathsToSearch(self):
325 def IgnoreDir(self, name):
326 return (super(SourceProcessor, self).IgnoreDir(name) or
327 name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
329 IGNORE_COPYRIGHTS = ['cpplint.py',
335 'libraries-empty.cc',
338 'gnuplot-4.6.3-emscripten.js']
339 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
341 def EndOfDeclaration(self, line):
342 return line == "}" or line == "};"
344 def StartOfDeclaration(self, line):
345 return line.find("//") == 0 or \
346 line.find("/*") == 0 or \
347 line.find(") {") != -1
349 def ProcessContents(self, name, contents):
351 base = basename(name)
352 if not base in SourceProcessor.IGNORE_TABS:
354 print "%s contains tabs" % name
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
360 if ' \n' in contents or contents.endswith(' '):
363 parts = contents.split(' \n')
364 if not contents.endswith(' '):
367 line += part.count('\n') + 1
368 lines.append(str(line))
369 linenumbers = ', '.join(lines)
371 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
373 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
375 if not contents.endswith('\n') or contents.endswith('\n\n'):
376 print "%s does not end with a single new line." % name
378 # Check two empty lines between declarations.
379 if name.endswith(".cc"):
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))
388 elif parts[line + 1] == "" and \
389 self.StartOfDeclaration(parts[line + 2]):
390 lines.append(str(line + 1))
394 linenumbers = ', '.join(lines)
396 print "%s does not have two empty lines between declarations " \
397 "in lines %s." % (name, linenumbers)
399 print "%s does not have two empty lines between declarations " \
400 "in line %s." % (name, linenumbers)
404 def ProcessFiles(self, files, path):
410 contents = handle.read()
411 if not self.ProcessContents(file, contents):
416 print "Total violating files: %s" % violations
420 def CheckRuntimeVsNativesNameClashes(workspace):
421 code = subprocess.call(
422 [sys.executable, join(workspace, "tools", "check-name-clashes.py")])
426 def CheckExternalReferenceRegistration(workspace):
427 code = subprocess.call(
428 [sys.executable, join(workspace, "tools", "external-reference-check.py")])
433 result = optparse.OptionParser()
434 result.add_option('--no-lint', help="Do not run cpplint", default=False,
440 workspace = abspath(join(dirname(sys.argv[0]), '..'))
441 parser = GetOptions()
442 (options, args) = parser.parse_args()
444 print "Running C++ lint check..."
445 if not options.no_lint:
446 success = CppLintProcessor().Run(workspace) and success
447 print "Running copyright header, trailing whitespaces and " \
448 "two empty lines between declarations check..."
449 success = SourceProcessor().Run(workspace) and success
450 success = CheckRuntimeVsNativesNameClashes(workspace) and success
451 success = CheckExternalReferenceRegistration(workspace) and success
458 if __name__ == '__main__':