The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / tools / presubmit.py @ f230a1cf

History | View | Annotate | Download (12.8 KB)

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/braces
63
readability/casting
64
readability/check
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/references
84
runtime/rtti
85
runtime/sizeof
86
runtime/string
87
runtime/virtual
88
runtime/vlog
89
whitespace/blank_line
90
whitespace/braces
91
whitespace/comma
92
whitespace/comments
93
whitespace/ending_newline
94
whitespace/indent
95
whitespace/labels
96
whitespace/line_length
97
whitespace/newline
98
whitespace/operators
99
whitespace/parens
100
whitespace/tab
101
whitespace/todo
102
""".split()
103

    
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 IOError:
148
        # File might not exist, this is OK.
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
    finally:
159
      sums_file.close()
160

    
161
  def FilterUnchangedFiles(self, files):
162
    changed_or_new = []
163
    for file in files:
164
      try:
165
        handle = open(file, "r")
166
        file_sum = md5er(handle.read()).digest()
167
        if not file in self.sums or self.sums[file] != file_sum:
168
          changed_or_new.append(file)
169
          self.sums[file] = file_sum
170
      finally:
171
        handle.close()
172
    return changed_or_new
173

    
174
  def RemoveFile(self, file):
175
    if file in self.sums:
176
      self.sums.pop(file)
177

    
178

    
179
class SourceFileProcessor(object):
180
  """
181
  Utility class that can run through a directory structure, find all relevant
182
  files and invoke a custom check on the files.
183
  """
184

    
185
  def Run(self, path):
186
    all_files = []
187
    for file in self.GetPathsToSearch():
188
      all_files += self.FindFilesIn(join(path, file))
189
    if not self.ProcessFiles(all_files, path):
190
      return False
191
    return True
192

    
193
  def IgnoreDir(self, name):
194
    return name.startswith('.') or name == 'data' or name == 'sputniktests'
195

    
196
  def IgnoreFile(self, name):
197
    return name.startswith('.')
198

    
199
  def FindFilesIn(self, path):
200
    result = []
201
    for (root, dirs, files) in os.walk(path):
202
      for ignored in [x for x in dirs if self.IgnoreDir(x)]:
203
        dirs.remove(ignored)
204
      for file in files:
205
        if not self.IgnoreFile(file) and self.IsRelevant(file):
206
          result.append(join(root, file))
207
    return result
208

    
209

    
210
class CppLintProcessor(SourceFileProcessor):
211
  """
212
  Lint files to check that they follow the google code style.
213
  """
214

    
215
  def IsRelevant(self, name):
216
    return name.endswith('.cc') or name.endswith('.h')
217

    
218
  def IgnoreDir(self, name):
219
    return (super(CppLintProcessor, self).IgnoreDir(name)
220
              or (name == 'third_party'))
221

    
222
  IGNORE_LINT = ['flag-definitions.h']
223

    
224
  def IgnoreFile(self, name):
225
    return (super(CppLintProcessor, self).IgnoreFile(name)
226
              or (name in CppLintProcessor.IGNORE_LINT))
227

    
228
  def GetPathsToSearch(self):
229
    return ['src', 'include', 'samples', join('test', 'cctest')]
230

    
231
  def GetCpplintScript(self, prio_path):
232
    for path in [prio_path] + os.environ["PATH"].split(os.pathsep):
233
      path = path.strip('"')
234
      cpplint = os.path.join(path, "cpplint.py")
235
      if os.path.isfile(cpplint):
236
        return cpplint
237

    
238
    return None
239

    
240
  def ProcessFiles(self, files, path):
241
    good_files_cache = FileContentsCache('.cpplint-cache')
242
    good_files_cache.Load()
243
    files = good_files_cache.FilterUnchangedFiles(files)
244
    if len(files) == 0:
245
      print 'No changes in files detected. Skipping cpplint check.'
246
      return True
247

    
248
    filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
249
    command = [sys.executable, 'cpplint.py', '--filter', filt]
250
    cpplint = self.GetCpplintScript(join(path, "tools"))
251
    if cpplint is None:
252
      print('Could not find cpplint.py. Make sure '
253
            'depot_tools is installed and in the path.')
254
      sys.exit(1)
255

    
256
    command = [sys.executable, cpplint, '--filter', filt]
257

    
258
    commands = join([command + [file] for file in files])
259
    count = multiprocessing.cpu_count()
260
    pool = multiprocessing.Pool(count)
261
    try:
262
      results = pool.map_async(CppLintWorker, commands).get(999999)
263
    except KeyboardInterrupt:
264
      print "\nCaught KeyboardInterrupt, terminating workers."
265
      sys.exit(1)
266

    
267
    for i in range(len(files)):
268
      if results[i] > 0:
269
        good_files_cache.RemoveFile(files[i])
270

    
271
    total_errors = sum(results)
272
    print "Total errors found: %d" % total_errors
273
    good_files_cache.Save()
274
    return total_errors == 0
275

    
276

    
277
COPYRIGHT_HEADER_PATTERN = re.compile(
278
    r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
279

    
280
class SourceProcessor(SourceFileProcessor):
281
  """
282
  Check that all files include a copyright notice and no trailing whitespaces.
283
  """
284

    
285
  RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c',
286
                         '.status', '.gyp', '.gypi']
287

    
288
  # Overwriting the one in the parent class.
289
  def FindFilesIn(self, path):
290
    if os.path.exists(path+'/.git'):
291
      output = subprocess.Popen('git ls-files --full-name',
292
                                stdout=PIPE, cwd=path, shell=True)
293
      result = []
294
      for file in output.stdout.read().split():
295
        for dir_part in os.path.dirname(file).replace(os.sep, '/').split('/'):
296
          if self.IgnoreDir(dir_part):
297
            break
298
        else:
299
          if self.IsRelevant(file) and not self.IgnoreFile(file):
300
            result.append(join(path, file))
301
      if output.wait() == 0:
302
        return result
303
    return super(SourceProcessor, self).FindFilesIn(path)
304

    
305
  def IsRelevant(self, name):
306
    for ext in SourceProcessor.RELEVANT_EXTENSIONS:
307
      if name.endswith(ext):
308
        return True
309
    return False
310

    
311
  def GetPathsToSearch(self):
312
    return ['.']
313

    
314
  def IgnoreDir(self, name):
315
    return (super(SourceProcessor, self).IgnoreDir(name)
316
              or (name == 'third_party')
317
              or (name == 'gyp')
318
              or (name == 'out')
319
              or (name == 'obj')
320
              or (name == 'DerivedSources'))
321

    
322
  IGNORE_COPYRIGHTS = ['cpplint.py',
323
                       'daemon.py',
324
                       'earley-boyer.js',
325
                       'raytrace.js',
326
                       'crypto.js',
327
                       'libraries.cc',
328
                       'libraries-empty.cc',
329
                       'jsmin.py',
330
                       'regexp-pcre.js',
331
                       'gnuplot-4.6.3-emscripten.js']
332
  IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
333

    
334
  def EndOfDeclaration(self, line):
335
    return line == "}" or line == "};"
336

    
337
  def StartOfDeclaration(self, line):
338
    return line.find("//") == 0 or \
339
           line.find("/*") == 0 or \
340
           line.find(") {") != -1
341

    
342
  def ProcessContents(self, name, contents):
343
    result = True
344
    base = basename(name)
345
    if not base in SourceProcessor.IGNORE_TABS:
346
      if '\t' in contents:
347
        print "%s contains tabs" % name
348
        result = False
349
    if not base in SourceProcessor.IGNORE_COPYRIGHTS:
350
      if not COPYRIGHT_HEADER_PATTERN.search(contents):
351
        print "%s is missing a correct copyright header." % name
352
        result = False
353
    if ' \n' in contents or contents.endswith(' '):
354
      line = 0
355
      lines = []
356
      parts = contents.split(' \n')
357
      if not contents.endswith(' '):
358
        parts.pop()
359
      for part in parts:
360
        line += part.count('\n') + 1
361
        lines.append(str(line))
362
      linenumbers = ', '.join(lines)
363
      if len(lines) > 1:
364
        print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
365
      else:
366
        print "%s has trailing whitespaces in line %s." % (name, linenumbers)
367
      result = False
368
    # Check two empty lines between declarations.
369
    if name.endswith(".cc"):
370
      line = 0
371
      lines = []
372
      parts = contents.split('\n')
373
      while line < len(parts) - 2:
374
        if self.EndOfDeclaration(parts[line]):
375
          if self.StartOfDeclaration(parts[line + 1]):
376
            lines.append(str(line + 1))
377
            line += 1
378
          elif parts[line + 1] == "" and \
379
               self.StartOfDeclaration(parts[line + 2]):
380
            lines.append(str(line + 1))
381
            line += 2
382
        line += 1
383
      if len(lines) >= 1:
384
        linenumbers = ', '.join(lines)
385
        if len(lines) > 1:
386
          print "%s does not have two empty lines between declarations " \
387
                "in lines %s." % (name, linenumbers)
388
        else:
389
          print "%s does not have two empty lines between declarations " \
390
                "in line %s." % (name, linenumbers)
391
        result = False
392
    return result
393

    
394
  def ProcessFiles(self, files, path):
395
    success = True
396
    violations = 0
397
    for file in files:
398
      try:
399
        handle = open(file)
400
        contents = handle.read()
401
        if not self.ProcessContents(file, contents):
402
          success = False
403
          violations += 1
404
      finally:
405
        handle.close()
406
    print "Total violating files: %s" % violations
407
    return success
408

    
409

    
410
def GetOptions():
411
  result = optparse.OptionParser()
412
  result.add_option('--no-lint', help="Do not run cpplint", default=False,
413
                    action="store_true")
414
  return result
415

    
416

    
417
def Main():
418
  workspace = abspath(join(dirname(sys.argv[0]), '..'))
419
  parser = GetOptions()
420
  (options, args) = parser.parse_args()
421
  success = True
422
  print "Running C++ lint check..."
423
  if not options.no_lint:
424
    success = CppLintProcessor().Run(workspace) and success
425
  print "Running copyright header, trailing whitespaces and " \
426
        "two empty lines between declarations check..."
427
  success = SourceProcessor().Run(workspace) and success
428
  if success:
429
    return 0
430
  else:
431
    return 1
432

    
433

    
434
if __name__ == '__main__':
435
  sys.exit(Main())