Upstream version 7.36.149.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_order
58 build/printf_format
59 build/storage_class
60 legal/copyright
61 readability/boost
62 readability/casting
63 readability/constructors
64 readability/fn_size
65 readability/function
66 readability/multiline_comment
67 readability/multiline_string
68 readability/streams
69 readability/todo
70 readability/utf8
71 runtime/arrays
72 runtime/casting
73 runtime/deprecated_fn
74 runtime/explicit
75 runtime/int
76 runtime/memset
77 runtime/mutex
78 runtime/nonconf
79 runtime/printf
80 runtime/printf_format
81 runtime/rtti
82 runtime/sizeof
83 runtime/string
84 runtime/virtual
85 runtime/vlog
86 whitespace/blank_line
87 whitespace/braces
88 whitespace/comma
89 whitespace/comments
90 whitespace/ending_newline
91 whitespace/labels
92 whitespace/line_length
93 whitespace/newline
94 whitespace/operators
95 whitespace/parens
96 whitespace/tab
97 whitespace/todo
98 """.split()
99
100
101 LINT_OUTPUT_PATTERN = re.compile(r'^.+[:(]\d+[:)]|^Done processing')
102
103
104 def CppLintWorker(command):
105   try:
106     process = subprocess.Popen(command, stderr=subprocess.PIPE)
107     process.wait()
108     out_lines = ""
109     error_count = -1
110     while True:
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()
115           return 1
116         break
117       m = LINT_OUTPUT_PATTERN.match(out_line)
118       if m:
119         out_lines += out_line
120         error_count += 1
121     sys.stdout.write(out_lines)
122     return error_count
123   except KeyboardInterrupt:
124     process.kill()
125   except:
126     print('Error running cpplint.py. Please make sure you have depot_tools' +
127           ' in your $PATH. Lint check skipped.')
128     process.kill()
129
130
131 class FileContentsCache(object):
132
133   def __init__(self, sums_file_name):
134     self.sums = {}
135     self.sums_file_name = sums_file_name
136
137   def Load(self):
138     try:
139       sums_file = None
140       try:
141         sums_file = open(self.sums_file_name, 'r')
142         self.sums = pickle.load(sums_file)
143       except:
144         # Cannot parse pickle for any reason. Not much we can do about it.
145         pass
146     finally:
147       if sums_file:
148         sums_file.close()
149
150   def Save(self):
151     try:
152       sums_file = open(self.sums_file_name, 'w')
153       pickle.dump(self.sums, sums_file)
154     except:
155       # Failed to write pickle. Try to clean-up behind us.
156       if sums_file:
157         sums_file.close()
158       try:
159         os.unlink(self.sums_file_name)
160       except:
161         pass
162     finally:
163       sums_file.close()
164
165   def FilterUnchangedFiles(self, files):
166     changed_or_new = []
167     for file in files:
168       try:
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
174       finally:
175         handle.close()
176     return changed_or_new
177
178   def RemoveFile(self, file):
179     if file in self.sums:
180       self.sums.pop(file)
181
182
183 class SourceFileProcessor(object):
184   """
185   Utility class that can run through a directory structure, find all relevant
186   files and invoke a custom check on the files.
187   """
188
189   def Run(self, path):
190     all_files = []
191     for file in self.GetPathsToSearch():
192       all_files += self.FindFilesIn(join(path, file))
193     if not self.ProcessFiles(all_files, path):
194       return False
195     return True
196
197   def IgnoreDir(self, name):
198     return (name.startswith('.') or
199             name in ('data', 'kraken', 'octane', 'sunspider'))
200
201   def IgnoreFile(self, name):
202     return name.startswith('.')
203
204   def FindFilesIn(self, path):
205     result = []
206     for (root, dirs, files) in os.walk(path):
207       for ignored in [x for x in dirs if self.IgnoreDir(x)]:
208         dirs.remove(ignored)
209       for file in files:
210         if not self.IgnoreFile(file) and self.IsRelevant(file):
211           result.append(join(root, file))
212     return result
213
214
215 class CppLintProcessor(SourceFileProcessor):
216   """
217   Lint files to check that they follow the google code style.
218   """
219
220   def IsRelevant(self, name):
221     return name.endswith('.cc') or name.endswith('.h')
222
223   def IgnoreDir(self, name):
224     return (super(CppLintProcessor, self).IgnoreDir(name)
225               or (name == 'third_party'))
226
227   IGNORE_LINT = ['flag-definitions.h']
228
229   def IgnoreFile(self, name):
230     return (super(CppLintProcessor, self).IgnoreFile(name)
231               or (name in CppLintProcessor.IGNORE_LINT))
232
233   def GetPathsToSearch(self):
234     return ['src', 'include', 'samples', join('test', 'cctest')]
235
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):
241         return cpplint
242
243     return None
244
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)
249     if len(files) == 0:
250       print 'No changes in files detected. Skipping cpplint check.'
251       return True
252
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"))
256     if cpplint is None:
257       print('Could not find cpplint.py. Make sure '
258             'depot_tools is installed and in the path.')
259       sys.exit(1)
260
261     command = [sys.executable, cpplint, '--filter', filt]
262
263     commands = join([command + [file] for file in files])
264     count = multiprocessing.cpu_count()
265     pool = multiprocessing.Pool(count)
266     try:
267       results = pool.map_async(CppLintWorker, commands).get(999999)
268     except KeyboardInterrupt:
269       print "\nCaught KeyboardInterrupt, terminating workers."
270       sys.exit(1)
271
272     for i in range(len(files)):
273       if results[i] > 0:
274         good_files_cache.RemoveFile(files[i])
275
276     total_errors = sum(results)
277     print "Total errors found: %d" % total_errors
278     good_files_cache.Save()
279     return total_errors == 0
280
281
282 COPYRIGHT_HEADER_PATTERN = re.compile(
283     r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
284
285 class SourceProcessor(SourceFileProcessor):
286   """
287   Check that all files include a copyright notice and no trailing whitespaces.
288   """
289
290   RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
291                          '.status', '.gyp', '.gypi']
292
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)
298       result = []
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):
302             break
303         else:
304           if self.IsRelevant(file) and not self.IgnoreFile(file):
305             result.append(join(path, file))
306       if output.wait() == 0:
307         return result
308     return super(SourceProcessor, self).FindFilesIn(path)
309
310   def IsRelevant(self, name):
311     for ext in SourceProcessor.RELEVANT_EXTENSIONS:
312       if name.endswith(ext):
313         return True
314     return False
315
316   def GetPathsToSearch(self):
317     return ['.']
318
319   def IgnoreDir(self, name):
320     return (super(SourceProcessor, self).IgnoreDir(name) or
321             name in ('third_party', 'gyp', 'out', 'obj', 'DerivedSources'))
322
323   IGNORE_COPYRIGHTS = ['cpplint.py',
324                        'daemon.py',
325                        'earley-boyer.js',
326                        'raytrace.js',
327                        'crypto.js',
328                        'libraries.cc',
329                        'libraries-empty.cc',
330                        'jsmin.py',
331                        'regexp-pcre.js',
332                        'gnuplot-4.6.3-emscripten.js']
333   IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
334
335   def EndOfDeclaration(self, line):
336     return line == "}" or line == "};"
337
338   def StartOfDeclaration(self, line):
339     return line.find("//") == 0 or \
340            line.find("/*") == 0 or \
341            line.find(") {") != -1
342
343   def ProcessContents(self, name, contents):
344     result = True
345     base = basename(name)
346     if not base in SourceProcessor.IGNORE_TABS:
347       if '\t' in contents:
348         print "%s contains tabs" % name
349         result = False
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
353         result = False
354     if ' \n' in contents or contents.endswith(' '):
355       line = 0
356       lines = []
357       parts = contents.split(' \n')
358       if not contents.endswith(' '):
359         parts.pop()
360       for part in parts:
361         line += part.count('\n') + 1
362         lines.append(str(line))
363       linenumbers = ', '.join(lines)
364       if len(lines) > 1:
365         print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
366       else:
367         print "%s has trailing whitespaces in line %s." % (name, linenumbers)
368       result = False
369     if not contents.endswith('\n') or contents.endswith('\n\n'):
370       print "%s does not end with a single new line." % name
371       result = False
372     # Check two empty lines between declarations.
373     if name.endswith(".cc"):
374       line = 0
375       lines = []
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))
381             line += 1
382           elif parts[line + 1] == "" and \
383                self.StartOfDeclaration(parts[line + 2]):
384             lines.append(str(line + 1))
385             line += 2
386         line += 1
387       if len(lines) >= 1:
388         linenumbers = ', '.join(lines)
389         if len(lines) > 1:
390           print "%s does not have two empty lines between declarations " \
391                 "in lines %s." % (name, linenumbers)
392         else:
393           print "%s does not have two empty lines between declarations " \
394                 "in line %s." % (name, linenumbers)
395         result = False
396     return result
397
398   def ProcessFiles(self, files, path):
399     success = True
400     violations = 0
401     for file in files:
402       try:
403         handle = open(file)
404         contents = handle.read()
405         if not self.ProcessContents(file, contents):
406           success = False
407           violations += 1
408       finally:
409         handle.close()
410     print "Total violating files: %s" % violations
411     return success
412
413
414 def GetOptions():
415   result = optparse.OptionParser()
416   result.add_option('--no-lint', help="Do not run cpplint", default=False,
417                     action="store_true")
418   return result
419
420
421 def Main():
422   workspace = abspath(join(dirname(sys.argv[0]), '..'))
423   parser = GetOptions()
424   (options, args) = parser.parse_args()
425   success = True
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
432   if success:
433     return 0
434   else:
435     return 1
436
437
438 if __name__ == '__main__':
439   sys.exit(Main())