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 # Special LINT rules diverging from default and reason.
49 # build/header_guard: Our guards have the form "V8_FOO_H_", not "SRC_FOO_H_".
50 # build/include_what_you_use: Started giving false positives for variables
51 # named "string" and "map" assuming that you needed to include STL headers.
52 # TODO(bmeurer): Fix and re-enable readability/check
53 # TODO(mstarzinger): Fix and re-enable readability/namespace
58 -build/include_what_you_use
61 -readability/inheritance
62 -readability/namespace
68 LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
69 FLAGS_LINE = re.compile("//\s*Flags:.*--([A-z0-9-])+_[A-z0-9].*\n")
71 def CppLintWorker(command):
73 process = subprocess.Popen(command, stderr=subprocess.PIPE)
78 out_line = process.stderr.readline()
79 if out_line == '' and process.poll() != None:
81 print "Failed to process %s" % command.pop()
84 m = LINT_OUTPUT_PATTERN.match(out_line)
88 sys.stdout.write(out_lines)
90 except KeyboardInterrupt:
93 print('Error running cpplint.py. Please make sure you have depot_tools' +
94 ' in your $PATH. Lint check skipped.')
98 class FileContentsCache(object):
100 def __init__(self, sums_file_name):
102 self.sums_file_name = sums_file_name
108 sums_file = open(self.sums_file_name, 'r')
109 self.sums = pickle.load(sums_file)
111 # Cannot parse pickle for any reason. Not much we can do about it.
119 sums_file = open(self.sums_file_name, 'w')
120 pickle.dump(self.sums, sums_file)
122 # Failed to write pickle. Try to clean-up behind us.
126 os.unlink(self.sums_file_name)
132 def FilterUnchangedFiles(self, files):
136 handle = open(file, "r")
137 file_sum = md5er(handle.read()).digest()
138 if not file in self.sums or self.sums[file] != file_sum:
139 changed_or_new.append(file)
140 self.sums[file] = file_sum
143 return changed_or_new
145 def RemoveFile(self, file):
146 if file in self.sums:
150 class SourceFileProcessor(object):
152 Utility class that can run through a directory structure, find all relevant
153 files and invoke a custom check on the files.
158 for file in self.GetPathsToSearch():
159 all_files += self.FindFilesIn(join(path, file))
160 if not self.ProcessFiles(all_files, path):
164 def IgnoreDir(self, name):
165 return (name.startswith('.') or
166 name in ('buildtools', 'data', 'gmock', 'gtest', 'kraken',
167 'octane', 'sunspider'))
169 def IgnoreFile(self, name):
170 return name.startswith('.')
172 def FindFilesIn(self, path):
174 for (root, dirs, files) in os.walk(path):
175 for ignored in [x for x in dirs if self.IgnoreDir(x)]:
178 if not self.IgnoreFile(file) and self.IsRelevant(file):
179 result.append(join(root, file))
183 class CppLintProcessor(SourceFileProcessor):
185 Lint files to check that they follow the google code style.
188 def IsRelevant(self, name):
189 return name.endswith('.cc') or name.endswith('.h')
191 def IgnoreDir(self, name):
192 return (super(CppLintProcessor, self).IgnoreDir(name)
193 or (name == 'third_party'))
195 IGNORE_LINT = ['flag-definitions.h']
197 def IgnoreFile(self, name):
198 return (super(CppLintProcessor, self).IgnoreFile(name)
199 or (name in CppLintProcessor.IGNORE_LINT))
201 def GetPathsToSearch(self):
202 return ['src', 'include', 'samples', join('test', 'cctest'),
203 join('test', 'unittests')]
205 def GetCpplintScript(self, prio_path):
206 for path in [prio_path] + os.environ["PATH"].split(os.pathsep):
207 path = path.strip('"')
208 cpplint = os.path.join(path, "cpplint.py")
209 if os.path.isfile(cpplint):
214 def ProcessFiles(self, files, path):
215 good_files_cache = FileContentsCache('.cpplint-cache')
216 good_files_cache.Load()
217 files = good_files_cache.FilterUnchangedFiles(files)
219 print 'No changes in files detected. Skipping cpplint check.'
222 filters = ",".join([n for n in LINT_RULES])
223 command = [sys.executable, 'cpplint.py', '--filter', filters]
224 cpplint = self.GetCpplintScript(join(path, "tools"))
226 print('Could not find cpplint.py. Make sure '
227 'depot_tools is installed and in the path.')
230 command = [sys.executable, cpplint, '--filter', filters]
232 commands = join([command + [file] for file in files])
233 count = multiprocessing.cpu_count()
234 pool = multiprocessing.Pool(count)
236 results = pool.map_async(CppLintWorker, commands).get(999999)
237 except KeyboardInterrupt:
238 print "\nCaught KeyboardInterrupt, terminating workers."
241 for i in range(len(files)):
243 good_files_cache.RemoveFile(files[i])
245 total_errors = sum(results)
246 print "Total errors found: %d" % total_errors
247 good_files_cache.Save()
248 return total_errors == 0
251 COPYRIGHT_HEADER_PATTERN = re.compile(
252 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
254 class SourceProcessor(SourceFileProcessor):
256 Check that all files include a copyright notice and no trailing whitespaces.
259 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
260 '.status', '.gyp', '.gypi']
262 # Overwriting the one in the parent class.
263 def FindFilesIn(self, path):
264 if os.path.exists(path+'/.git'):
265 output = subprocess.Popen('git ls-files --full-name',
266 stdout=PIPE, cwd=path, shell=True)
268 for file in output.stdout.read().split():
269 for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
270 if self.IgnoreDir(dir_part):
273 if (self.IsRelevant(file) and os.path.exists(file)
274 and not self.IgnoreFile(file)):
275 result.append(join(path, file))
276 if output.wait() == 0:
278 return super(SourceProcessor, self).FindFilesIn(path)
280 def IsRelevant(self, name):
281 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
282 if name.endswith(ext):
286 def GetPathsToSearch(self):
289 def IgnoreDir(self, name):
290 return (super(SourceProcessor, self).IgnoreDir(name) or
291 name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
293 IGNORE_COPYRIGHTS = ['box2d.js',
304 'libraries-empty.cc',
305 'lua_binarytrees.js',
312 'sqlite-change-heap.js',
313 'sqlite-pointer-masking.js',
314 'sqlite-safe-heap.js',
315 'gnuplot-4.6.3-emscripten.js',
317 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
319 def EndOfDeclaration(self, line):
320 return line == "}" or line == "};"
322 def StartOfDeclaration(self, line):
323 return line.find("//") == 0 or \
324 line.find("/*") == 0 or \
325 line.find(") {") != -1
327 def ProcessContents(self, name, contents):
329 base = basename(name)
330 if not base in SourceProcessor.IGNORE_TABS:
332 print "%s contains tabs" % name
334 if not base in SourceProcessor.IGNORE_COPYRIGHTS:
335 if not COPYRIGHT_HEADER_PATTERN.search(contents):
336 print "%s is missing a correct copyright header." % name
338 if ' \n' in contents or contents.endswith(' '):
341 parts = contents.split(' \n')
342 if not contents.endswith(' '):
345 line += part.count('\n') + 1
346 lines.append(str(line))
347 linenumbers = ', '.join(lines)
349 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
351 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
353 if not contents.endswith('\n') or contents.endswith('\n\n'):
354 print "%s does not end with a single new line." % name
356 # Check two empty lines between declarations.
357 if name.endswith(".cc"):
360 parts = contents.split('\n')
361 while line < len(parts) - 2:
362 if self.EndOfDeclaration(parts[line]):
363 if self.StartOfDeclaration(parts[line + 1]):
364 lines.append(str(line + 1))
366 elif parts[line + 1] == "" and \
367 self.StartOfDeclaration(parts[line + 2]):
368 lines.append(str(line + 1))
372 linenumbers = ', '.join(lines)
374 print "%s does not have two empty lines between declarations " \
375 "in lines %s." % (name, linenumbers)
377 print "%s does not have two empty lines between declarations " \
378 "in line %s." % (name, linenumbers)
380 # Sanitize flags for fuzzer.
381 if "mjsunit" in name:
382 match = FLAGS_LINE.search(contents)
384 print "%s Flags should use '-' (not '_')" % name
388 def ProcessFiles(self, files, path):
394 contents = handle.read()
395 if not self.ProcessContents(file, contents):
400 print "Total violating files: %s" % violations
404 def CheckExternalReferenceRegistration(workspace):
405 code = subprocess.call(
406 [sys.executable, join(workspace, "tools", "external-reference-check.py")])
409 def CheckAuthorizedAuthor(input_api, output_api):
410 """For non-googler/chromites committers, verify the author's email address is
413 # TODO(maruel): Add it to input_api?
416 author = input_api.change.author_email
418 input_api.logging.info('No author, skipping AUTHOR check')
420 authors_path = input_api.os_path.join(
421 input_api.PresubmitLocalPath(), 'AUTHORS')
423 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
424 for line in open(authors_path))
425 valid_authors = [item.group(1).lower() for item in valid_authors if item]
426 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
427 input_api.logging.info('Valid authors are %s', ', '.join(valid_authors))
428 return [output_api.PresubmitPromptWarning(
429 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
431 'http://www.chromium.org/developers/contributing-code and read the '
433 'If you are a chromite, verify the contributor signed the CLA.') %
438 result = optparse.OptionParser()
439 result.add_option('--no-lint', help="Do not run cpplint", default=False,
445 workspace = abspath(join(dirname(sys.argv[0]), '..'))
446 parser = GetOptions()
447 (options, args) = parser.parse_args()
449 print "Running C++ lint check..."
450 if not options.no_lint:
451 success = CppLintProcessor().Run(workspace) and success
452 print "Running copyright header, trailing whitespaces and " \
453 "two empty lines between declarations check..."
454 success = SourceProcessor().Run(workspace) and success
455 success = CheckExternalReferenceRegistration(workspace) and success
462 if __name__ == '__main__':