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 = """
63 readability/constructors
66 readability/multiline_comment
67 readability/multiline_string
90 whitespace/ending_newline
92 whitespace/line_length
101 LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
104 def CppLintWorker(command):
106 process = subprocess.Popen(command, stderr=subprocess.PIPE)
111 out_line = process.stderr.readline()
112 if out_line == '' and process.poll() != None:
113 if error_count == -1:
114 print "Failed to process %s" % command.pop()
117 m = LINT_OUTPUT_PATTERN.match(out_line)
119 out_lines += out_line
121 sys.stdout.write(out_lines)
123 except KeyboardInterrupt:
126 print('Error running cpplint.py. Please make sure you have depot_tools' +
127 ' in your $PATH. Lint check skipped.')
131 class FileContentsCache(object):
133 def __init__(self, sums_file_name):
135 self.sums_file_name = sums_file_name
141 sums_file = open(self.sums_file_name, 'r')
142 self.sums = pickle.load(sums_file)
144 # Cannot parse pickle for any reason. Not much we can do about it.
152 sums_file = open(self.sums_file_name, 'w')
153 pickle.dump(self.sums, sums_file)
155 # Failed to write pickle. Try to clean-up behind us.
159 os.unlink(self.sums_file_name)
165 def FilterUnchangedFiles(self, files):
169 handle = open(file, "r")
170 file_sum = md5er(handle.read()).digest()
171 if not file in self.sums or self.sums[file] != file_sum:
172 changed_or_new.append(file)
173 self.sums[file] = file_sum
176 return changed_or_new
178 def RemoveFile(self, file):
179 if file in self.sums:
183 class SourceFileProcessor(object):
185 Utility class that can run through a directory structure, find all relevant
186 files and invoke a custom check on the files.
191 for file in self.GetPathsToSearch():
192 all_files += self.FindFilesIn(join(path, file))
193 if not self.ProcessFiles(all_files, path):
197 def IgnoreDir(self, name):
198 return (name.startswith('.') or
199 name in ('data', 'kraken', 'octane', 'sunspider'))
201 def IgnoreFile(self, name):
202 return name.startswith('.')
204 def FindFilesIn(self, path):
206 for (root, dirs, files) in os.walk(path):
207 for ignored in [x for x in dirs if self.IgnoreDir(x)]:
210 if not self.IgnoreFile(file) and self.IsRelevant(file):
211 result.append(join(root, file))
215 class CppLintProcessor(SourceFileProcessor):
217 Lint files to check that they follow the google code style.
220 def IsRelevant(self, name):
221 return name.endswith('.cc') or name.endswith('.h')
223 def IgnoreDir(self, name):
224 return (super(CppLintProcessor, self).IgnoreDir(name)
225 or (name == 'third_party'))
227 IGNORE_LINT = ['flag-definitions.h']
229 def IgnoreFile(self, name):
230 return (super(CppLintProcessor, self).IgnoreFile(name)
231 or (name in CppLintProcessor.IGNORE_LINT))
233 def GetPathsToSearch(self):
234 return ['src', 'include', 'samples', join('test', 'cctest')]
236 def GetCpplintScript(self, prio_path):
237 for path in [prio_path] + os.environ["PATH"].split(os.pathsep):
238 path = path.strip('"')
239 cpplint = os.path.join(path, "cpplint.py")
240 if os.path.isfile(cpplint):
245 def ProcessFiles(self, files, path):
246 good_files_cache = FileContentsCache('.cpplint-cache')
247 good_files_cache.Load()
248 files = good_files_cache.FilterUnchangedFiles(files)
250 print 'No changes in files detected. Skipping cpplint check.'
253 filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
254 command = [sys.executable, 'cpplint.py', '--filter', filt]
255 cpplint = self.GetCpplintScript(join(path, "tools"))
257 print('Could not find cpplint.py. Make sure '
258 'depot_tools is installed and in the path.')
261 command = [sys.executable, cpplint, '--filter', filt]
263 commands = join([command + [file] for file in files])
264 count = multiprocessing.cpu_count()
265 pool = multiprocessing.Pool(count)
267 results = pool.map_async(CppLintWorker, commands).get(999999)
268 except KeyboardInterrupt:
269 print "\nCaught KeyboardInterrupt, terminating workers."
272 for i in range(len(files)):
274 good_files_cache.RemoveFile(files[i])
276 total_errors = sum(results)
277 print "Total errors found: %d" % total_errors
278 good_files_cache.Save()
279 return total_errors == 0
282 COPYRIGHT_HEADER_PATTERN = re.compile(
283 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
285 class SourceProcessor(SourceFileProcessor):
287 Check that all files include a copyright notice and no trailing whitespaces.
290 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
291 '.status', '.gyp', '.gypi']
293 # Overwriting the one in the parent class.
294 def FindFilesIn(self, path):
295 if os.path.exists(path+'/.git'):
296 output = subprocess.Popen('git ls-files --full-name',
297 stdout=PIPE, cwd=path, shell=True)
299 for file in output.stdout.read().split():
300 for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
301 if self.IgnoreDir(dir_part):
304 if self.IsRelevant(file) and not self.IgnoreFile(file):
305 result.append(join(path, file))
306 if output.wait() == 0:
308 return super(SourceProcessor, self).FindFilesIn(path)
310 def IsRelevant(self, name):
311 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
312 if name.endswith(ext):
316 def GetPathsToSearch(self):
319 def IgnoreDir(self, name):
320 return (super(SourceProcessor, self).IgnoreDir(name) or
321 name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
323 IGNORE_COPYRIGHTS = ['cpplint.py',
329 'libraries-empty.cc',
332 'gnuplot-4.6.3-emscripten.js']
333 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
335 def EndOfDeclaration(self, line):
336 return line == "}" or line == "};"
338 def StartOfDeclaration(self, line):
339 return line.find("//") == 0 or \
340 line.find("/*") == 0 or \
341 line.find(") {") != -1
343 def ProcessContents(self, name, contents):
345 base = basename(name)
346 if not base in SourceProcessor.IGNORE_TABS:
348 print "%s contains tabs" % name
350 if not base in SourceProcessor.IGNORE_COPYRIGHTS:
351 if not COPYRIGHT_HEADER_PATTERN.search(contents):
352 print "%s is missing a correct copyright header." % name
354 if ' \n' in contents or contents.endswith(' '):
357 parts = contents.split(' \n')
358 if not contents.endswith(' '):
361 line += part.count('\n') + 1
362 lines.append(str(line))
363 linenumbers = ', '.join(lines)
365 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
367 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
369 if not contents.endswith('\n') or contents.endswith('\n\n'):
370 print "%s does not end with a single new line." % name
372 # Check two empty lines between declarations.
373 if name.endswith(".cc"):
376 parts = contents.split('\n')
377 while line < len(parts) - 2:
378 if self.EndOfDeclaration(parts[line]):
379 if self.StartOfDeclaration(parts[line + 1]):
380 lines.append(str(line + 1))
382 elif parts[line + 1] == "" and \
383 self.StartOfDeclaration(parts[line + 2]):
384 lines.append(str(line + 1))
388 linenumbers = ', '.join(lines)
390 print "%s does not have two empty lines between declarations " \
391 "in lines %s." % (name, linenumbers)
393 print "%s does not have two empty lines between declarations " \
394 "in line %s." % (name, linenumbers)
398 def ProcessFiles(self, files, path):
404 contents = handle.read()
405 if not self.ProcessContents(file, contents):
410 print "Total violating files: %s" % violations
415 result = optparse.OptionParser()
416 result.add_option('--no-lint', help="Do not run cpplint", default=False,
422 workspace = abspath(join(dirname(sys.argv[0]), '..'))
423 parser = GetOptions()
424 (options, args) = parser.parse_args()
426 print "Running C++ lint check..."
427 if not options.no_lint:
428 success = CppLintProcessor().Run(workspace) and success
429 print "Running copyright header, trailing whitespaces and " \
430 "two empty lines between declarations check..."
431 success = SourceProcessor().Run(workspace) and success
438 if __name__ == '__main__':