Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / v8 / tools / presubmit.py
1 #!/usr/bin/env python
2 #
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
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
30 try:
31   import hashlib
32   md5er = hashlib.md5
33 except ImportError, e:
34   import md5
35   md5er = md5.new
36
37
38 import optparse
39 import os
40 from os.path import abspath, join, dirname, basename, exists
41 import pickle
42 import re
43 import sys
44 import subprocess
45 import multiprocessing
46 from subprocess import PIPE
47
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
52 ENABLED_LINT_RULES = """
53 build/class
54 build/deprecated
55 build/endif_comment
56 build/forward_decl
57 build/include_alpha
58 build/include_order
59 build/printf_format
60 build/storage_class
61 legal/copyright
62 readability/boost
63 readability/braces
64 readability/casting
65 readability/constructors
66 readability/fn_size
67 readability/function
68 readability/multiline_comment
69 readability/multiline_string
70 readability/streams
71 readability/todo
72 readability/utf8
73 runtime/arrays
74 runtime/casting
75 runtime/deprecated_fn
76 runtime/explicit
77 runtime/int
78 runtime/memset
79 runtime/mutex
80 runtime/nonconf
81 runtime/printf
82 runtime/printf_format
83 runtime/rtti
84 runtime/sizeof
85 runtime/string
86 runtime/virtual
87 runtime/vlog
88 whitespace/blank_line
89 whitespace/braces
90 whitespace/comma
91 whitespace/comments
92 whitespace/ending_newline
93 whitespace/indent
94 whitespace/labels
95 whitespace/line_length
96 whitespace/newline
97 whitespace/operators
98 whitespace/parens
99 whitespace/tab
100 whitespace/todo
101 """.split()
102
103 # TODO(bmeurer): Fix and re-enable readability/check
104
105 LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
106
107
108 def 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:
117         if error_count == -1:
118           print "Failed to process %s" % command.pop()
119           return 1
120         break
121       m = LINT_OUTPUT_PATTERN.match(out_line)
122       if m:
123         out_lines += out_line
124         error_count += 1
125     sys.stdout.write(out_lines)
126     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
135 class 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)
147       except:
148         # Cannot parse pickle for any reason. Not much we can do about it.
149         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)
158     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
166     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")
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
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
187 class 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):
202     return (name.startswith('.') or
203             name in ('buildtools', 'data', 'gmock', 'gtest', 'kraken',
204                      'octane', 'sunspider'))
205
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
220 class 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']
233
234   def IgnoreFile(self, name):
235     return (super(CppLintProcessor, self).IgnoreFile(name)
236               or (name in CppLintProcessor.IGNORE_LINT))
237
238   def GetPathsToSearch(self):
239     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
249
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)
254     if len(files) == 0:
255       print 'No changes in files detected. Skipping cpplint check.'
256       return True
257
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"))
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]
267
268     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)
276
277     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
283     good_files_cache.Save()
284     return total_errors == 0
285
286
287 COPYRIGHT_HEADER_PATTERN = re.compile(
288     r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
289
290 class SourceProcessor(SourceFileProcessor):
291   """
292   Check that all files include a copyright notice and no trailing whitespaces.
293   """
294
295   RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
296                          '.status', '.gyp', '.gypi']
297
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():
305         for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
306           if self.IgnoreDir(dir_part):
307             break
308         else:
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:
313         return result
314     return super(SourceProcessor, self).FindFilesIn(path)
315
316   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):
326     return (super(SourceProcessor, self).IgnoreDir(name) or
327             name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
328
329   IGNORE_COPYRIGHTS = ['cpplint.py',
330                        'daemon.py',
331                        'earley-boyer.js',
332                        'raytrace.js',
333                        'crypto.js',
334                        'libraries.cc',
335                        'libraries-empty.cc',
336                        'jsmin.py',
337                        'regexp-pcre.js',
338                        'gnuplot-4.6.3-emscripten.js']
339   IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
340
341   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
349   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
360     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
375     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
402     return result
403
404   def ProcessFiles(self, files, path):
405     success = True
406     violations = 0
407     for file in files:
408       try:
409         handle = open(file)
410         contents = handle.read()
411         if not self.ProcessContents(file, contents):
412           success = False
413           violations += 1
414       finally:
415         handle.close()
416     print "Total violating files: %s" % violations
417     return success
418
419
420 def CheckRuntimeVsNativesNameClashes(workspace):
421   code = subprocess.call(
422       [sys.executable, join(workspace, "tools", "check-name-clashes.py")])
423   return code == 0
424
425
426 def CheckExternalReferenceRegistration(workspace):
427   code = subprocess.call(
428       [sys.executable, join(workspace, "tools", "external-reference-check.py")])
429   return code == 0
430
431
432 def 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
439 def Main():
440   workspace = abspath(join(dirname(sys.argv[0]), '..'))
441   parser = GetOptions()
442   (options, args) = parser.parse_args()
443   success = True
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
452   if success:
453     return 0
454   else:
455     return 1
456
457
458 if __name__ == '__main__':
459   sys.exit(Main())