7af6e3d0d80e3a85c7606af692c8b1c249e78922
[profile/ivi/qtjsbackend.git] / src / 3rdparty / v8 / tools / presubmit.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2011 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 from subprocess import PIPE
46
47 # Disabled LINT rules and reason.
48 # build/include_what_you_use: Started giving false positives for variables
49 #  named "string" and "map" assuming that you needed to include STL headers.
50
51 ENABLED_LINT_RULES = """
52 build/class
53 build/deprecated
54 build/endif_comment
55 build/forward_decl
56 build/include_order
57 build/printf_format
58 build/storage_class
59 legal/copyright
60 readability/boost
61 readability/braces
62 readability/casting
63 readability/check
64 readability/constructors
65 readability/fn_size
66 readability/function
67 readability/multiline_comment
68 readability/multiline_string
69 readability/streams
70 readability/todo
71 readability/utf8
72 runtime/arrays
73 runtime/casting
74 runtime/deprecated_fn
75 runtime/explicit
76 runtime/int
77 runtime/memset
78 runtime/mutex
79 runtime/nonconf
80 runtime/printf
81 runtime/printf_format
82 runtime/references
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
104 class FileContentsCache(object):
105
106   def __init__(self, sums_file_name):
107     self.sums = {}
108     self.sums_file_name = sums_file_name
109
110   def Load(self):
111     try:
112       sums_file = None
113       try:
114         sums_file = open(self.sums_file_name, 'r')
115         self.sums = pickle.load(sums_file)
116       except IOError:
117         # File might not exist, this is OK.
118         pass
119     finally:
120       if sums_file:
121         sums_file.close()
122
123   def Save(self):
124     try:
125       sums_file = open(self.sums_file_name, 'w')
126       pickle.dump(self.sums, sums_file)
127     finally:
128       sums_file.close()
129
130   def FilterUnchangedFiles(self, files):
131     changed_or_new = []
132     for file in files:
133       try:
134         handle = open(file, "r")
135         file_sum = md5er(handle.read()).digest()
136         if not file in self.sums or self.sums[file] != file_sum:
137           changed_or_new.append(file)
138           self.sums[file] = file_sum
139       finally:
140         handle.close()
141     return changed_or_new
142
143   def RemoveFile(self, file):
144     if file in self.sums:
145       self.sums.pop(file)
146
147
148 class SourceFileProcessor(object):
149   """
150   Utility class that can run through a directory structure, find all relevant
151   files and invoke a custom check on the files.
152   """
153
154   def Run(self, path):
155     all_files = []
156     for file in self.GetPathsToSearch():
157       all_files += self.FindFilesIn(join(path, file))
158     if not self.ProcessFiles(all_files, path):
159       return False
160     return True
161
162   def IgnoreDir(self, name):
163     return name.startswith('.') or name == 'data' or name == 'sputniktests'
164
165   def IgnoreFile(self, name):
166     return name.startswith('.')
167
168   def FindFilesIn(self, path):
169     result = []
170     for (root, dirs, files) in os.walk(path):
171       for ignored in [x for x in dirs if self.IgnoreDir(x)]:
172         dirs.remove(ignored)
173       for file in files:
174         if not self.IgnoreFile(file) and self.IsRelevant(file):
175           result.append(join(root, file))
176     return result
177
178
179 class CppLintProcessor(SourceFileProcessor):
180   """
181   Lint files to check that they follow the google code style.
182   """
183
184   def IsRelevant(self, name):
185     return name.endswith('.cc') or name.endswith('.h')
186
187   def IgnoreDir(self, name):
188     return (super(CppLintProcessor, self).IgnoreDir(name)
189               or (name == 'third_party'))
190
191   IGNORE_LINT = ['flag-definitions.h']
192
193   def IgnoreFile(self, name):
194     return (super(CppLintProcessor, self).IgnoreFile(name)
195               or (name in CppLintProcessor.IGNORE_LINT))
196
197   def GetPathsToSearch(self):
198     return ['src', 'preparser', 'include', 'samples', join('test', 'cctest')]
199
200   def ProcessFiles(self, files, path):
201     good_files_cache = FileContentsCache('.cpplint-cache')
202     good_files_cache.Load()
203     files = good_files_cache.FilterUnchangedFiles(files)
204     if len(files) == 0:
205       print 'No changes in files detected. Skipping cpplint check.'
206       return True
207
208     filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
209     command = ['cpplint.py', '--filter', filt] + join(files)
210     local_cpplint = join(path, "tools", "cpplint.py")
211     if exists(local_cpplint):
212       command = ['python', local_cpplint, '--filter', filt] + join(files)
213
214     try:
215       process = subprocess.Popen(command, stderr=subprocess.PIPE)
216     except:
217       print('Error running cpplint.py. Please make sure you have depot_tools' +
218             ' in your $PATH. Lint check skipped.')
219       return True
220     LINT_ERROR_PATTERN = re.compile(r'^(.+)[:(]\d+[:)]')
221     while True:
222       out_line = process.stderr.readline()
223       if out_line == '' and process.poll() != None:
224         break
225       sys.stderr.write(out_line)
226       m = LINT_ERROR_PATTERN.match(out_line)
227       if m:
228         good_files_cache.RemoveFile(m.group(1))
229
230     good_files_cache.Save()
231     return process.returncode == 0
232
233
234 COPYRIGHT_HEADER_PATTERN = re.compile(
235     r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
236
237 class SourceProcessor(SourceFileProcessor):
238   """
239   Check that all files include a copyright notice and no trailing whitespaces.
240   """
241
242   RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c', 'SConscript',
243       'SConstruct', '.status', '.gyp', '.gypi']
244
245   # Overwriting the one in the parent class.
246   def FindFilesIn(self, path):
247     if os.path.exists(path+'/.git'):
248       output = subprocess.Popen('git ls-files --full-name',
249                                 stdout=PIPE, cwd=path, shell=True)
250       result = []
251       for file in output.stdout.read().split():
252         for dir_part in os.path.dirname(file).split(os.sep):
253           if self.IgnoreDir(dir_part):
254             break
255         else:
256           if self.IsRelevant(file) and not self.IgnoreFile(file):
257             result.append(join(path, file))
258       if output.wait() == 0:
259         return result
260     return super(SourceProcessor, self).FindFilesIn(path)
261
262   def IsRelevant(self, name):
263     for ext in SourceProcessor.RELEVANT_EXTENSIONS:
264       if name.endswith(ext):
265         return True
266     return False
267
268   def GetPathsToSearch(self):
269     return ['.']
270
271   def IgnoreDir(self, name):
272     return (super(SourceProcessor, self).IgnoreDir(name)
273               or (name == 'third_party')
274               or (name == 'gyp')
275               or (name == 'out')
276               or (name == 'obj'))
277
278   IGNORE_COPYRIGHTS = ['cpplint.py',
279                        'earley-boyer.js',
280                        'raytrace.js',
281                        'crypto.js',
282                        'libraries.cc',
283                        'libraries-empty.cc',
284                        'jsmin.py',
285                        'regexp-pcre.js']
286   IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
287
288   def ProcessContents(self, name, contents):
289     result = True
290     base = basename(name)
291     if not base in SourceProcessor.IGNORE_TABS:
292       if '\t' in contents:
293         print "%s contains tabs" % name
294         result = False
295     if not base in SourceProcessor.IGNORE_COPYRIGHTS:
296       if not COPYRIGHT_HEADER_PATTERN.search(contents):
297         print "%s is missing a correct copyright header." % name
298         result = False
299     ext = base.split('.').pop()
300     if ' \n' in contents or contents.endswith(' '):
301       line = 0
302       lines = []
303       parts = contents.split(' \n')
304       if not contents.endswith(' '):
305         parts.pop()
306       for part in parts:
307         line += part.count('\n') + 1
308         lines.append(str(line))
309       linenumbers = ', '.join(lines)
310       if len(lines) > 1:
311         print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
312       else:
313         print "%s has trailing whitespaces in line %s." % (name, linenumbers)
314       result = False
315     return result
316
317   def ProcessFiles(self, files, path):
318     success = True
319     violations = 0
320     for file in files:
321       try:
322         handle = open(file)
323         contents = handle.read()
324         if not self.ProcessContents(file, contents):
325           success = False
326           violations += 1
327       finally:
328         handle.close()
329     print "Total violating files: %s" % violations
330     return success
331
332
333 def GetOptions():
334   result = optparse.OptionParser()
335   result.add_option('--no-lint', help="Do not run cpplint", default=False,
336                     action="store_true")
337   return result
338
339
340 def Main():
341   workspace = abspath(join(dirname(sys.argv[0]), '..'))
342   parser = GetOptions()
343   (options, args) = parser.parse_args()
344   success = True
345   print "Running C++ lint check..."
346   if not options.no_lint:
347     success = CppLintProcessor().Run(workspace) and success
348   print "Running copyright header and trailing whitespaces check..."
349   success = SourceProcessor().Run(workspace) and success
350   if success:
351     return 0
352   else:
353     return 1
354
355
356 if __name__ == '__main__':
357   sys.exit(Main())