• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2008 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
31import md5
32import optparse
33import os
34from os.path import abspath, join, dirname, basename, exists
35import pickle
36import re
37import sys
38import subprocess
39
40# Disabled LINT rules and reason.
41# build/include_what_you_use: Started giving false positives for variables
42#  named "string" and "map" assuming that you needed to include STL headers.
43
44ENABLED_LINT_RULES = """
45build/class
46build/deprecated
47build/endif_comment
48build/forward_decl
49build/include_order
50build/printf_format
51build/storage_class
52legal/copyright
53readability/boost
54readability/braces
55readability/casting
56readability/check
57readability/constructors
58readability/fn_size
59readability/function
60readability/multiline_comment
61readability/multiline_string
62readability/streams
63readability/todo
64readability/utf8
65runtime/arrays
66runtime/casting
67runtime/deprecated_fn
68runtime/explicit
69runtime/int
70runtime/memset
71runtime/mutex
72runtime/nonconf
73runtime/printf
74runtime/printf_format
75runtime/references
76runtime/rtti
77runtime/sizeof
78runtime/string
79runtime/virtual
80runtime/vlog
81whitespace/blank_line
82whitespace/braces
83whitespace/comma
84whitespace/comments
85whitespace/end_of_line
86whitespace/ending_newline
87whitespace/indent
88whitespace/labels
89whitespace/line_length
90whitespace/newline
91whitespace/operators
92whitespace/parens
93whitespace/tab
94whitespace/todo
95""".split()
96
97
98class FileContentsCache(object):
99
100  def __init__(self, sums_file_name):
101    self.sums = {}
102    self.sums_file_name = sums_file_name
103
104  def Load(self):
105    try:
106      sums_file = None
107      try:
108        sums_file = open(self.sums_file_name, 'r')
109        self.sums = pickle.load(sums_file)
110      except IOError:
111        # File might not exist, this is OK.
112        pass
113    finally:
114      if sums_file:
115        sums_file.close()
116
117  def Save(self):
118    try:
119      sums_file = open(self.sums_file_name, 'w')
120      pickle.dump(self.sums, sums_file)
121    finally:
122      sums_file.close()
123
124  def FilterUnchangedFiles(self, files):
125    changed_or_new = []
126    for file in files:
127      try:
128        handle = open(file, "r")
129        file_sum = md5.new(handle.read()).digest()
130        if not file in self.sums or self.sums[file] != file_sum:
131          changed_or_new.append(file)
132          self.sums[file] = file_sum
133      finally:
134        handle.close()
135    return changed_or_new
136
137  def RemoveFile(self, file):
138    if file in self.sums:
139      self.sums.pop(file)
140
141
142class SourceFileProcessor(object):
143  """
144  Utility class that can run through a directory structure, find all relevant
145  files and invoke a custom check on the files.
146  """
147
148  def Run(self, path):
149    all_files = []
150    for file in self.GetPathsToSearch():
151      all_files += self.FindFilesIn(join(path, file))
152    if not self.ProcessFiles(all_files, path):
153      return False
154    return True
155
156  def IgnoreDir(self, name):
157    return name.startswith('.') or name == 'data' or name == 'sputniktests'
158
159  def IgnoreFile(self, name):
160    return name.startswith('.')
161
162  def FindFilesIn(self, path):
163    result = []
164    for (root, dirs, files) in os.walk(path):
165      for ignored in [x for x in dirs if self.IgnoreDir(x)]:
166        dirs.remove(ignored)
167      for file in files:
168        if not self.IgnoreFile(file) and self.IsRelevant(file):
169          result.append(join(root, file))
170    return result
171
172
173class CppLintProcessor(SourceFileProcessor):
174  """
175  Lint files to check that they follow the google code style.
176  """
177
178  def IsRelevant(self, name):
179    return name.endswith('.cc') or name.endswith('.h')
180
181  def IgnoreDir(self, name):
182    return (super(CppLintProcessor, self).IgnoreDir(name)
183              or (name == 'third_party'))
184
185  IGNORE_LINT = ['flag-definitions.h']
186
187  def IgnoreFile(self, name):
188    return (super(CppLintProcessor, self).IgnoreFile(name)
189              or (name in CppLintProcessor.IGNORE_LINT))
190
191  def GetPathsToSearch(self):
192    return ['src', 'public', 'samples', join('test', 'cctest')]
193
194  def ProcessFiles(self, files, path):
195    good_files_cache = FileContentsCache('.cpplint-cache')
196    good_files_cache.Load()
197    files = good_files_cache.FilterUnchangedFiles(files)
198    if len(files) == 0:
199      print 'No changes in files detected. Skipping cpplint check.'
200      return True
201
202    filt = '-,' + ",".join(['+' + n for n in ENABLED_LINT_RULES])
203    command = ['cpplint.py', '--filter', filt] + join(files)
204    local_cpplint = join(path, "tools", "cpplint.py")
205    if exists(local_cpplint):
206      command = ['python', local_cpplint, '--filter', filt] + join(files)
207
208    process = subprocess.Popen(command, stderr=subprocess.PIPE)
209    LINT_ERROR_PATTERN = re.compile(r'^(.+)[:(]\d+[:)]')
210    while True:
211      out_line = process.stderr.readline()
212      if out_line == '' and process.poll() != None:
213        break
214      sys.stderr.write(out_line)
215      m = LINT_ERROR_PATTERN.match(out_line)
216      if m:
217        good_files_cache.RemoveFile(m.group(1))
218
219    good_files_cache.Save()
220    return process.returncode == 0
221
222
223COPYRIGHT_HEADER_PATTERN = re.compile(
224    r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
225
226class SourceProcessor(SourceFileProcessor):
227  """
228  Check that all files include a copyright notice.
229  """
230
231  RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c', 'SConscript',
232      'SConstruct', '.status']
233  def IsRelevant(self, name):
234    for ext in SourceProcessor.RELEVANT_EXTENSIONS:
235      if name.endswith(ext):
236        return True
237    return False
238
239  def GetPathsToSearch(self):
240    return ['.']
241
242  def IgnoreDir(self, name):
243    return (super(SourceProcessor, self).IgnoreDir(name)
244              or (name == 'third_party')
245              or (name == 'obj'))
246
247  IGNORE_COPYRIGHTS = ['earley-boyer.js', 'raytrace.js', 'crypto.js',
248      'libraries.cc', 'libraries-empty.cc', 'jsmin.py', 'regexp-pcre.js']
249  IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js',
250      'html-comments.js']
251
252  def ProcessContents(self, name, contents):
253    result = True
254    base = basename(name)
255    if not base in SourceProcessor.IGNORE_TABS:
256      if '\t' in contents:
257        print "%s contains tabs" % name
258        result = False
259    if not base in SourceProcessor.IGNORE_COPYRIGHTS:
260      if not COPYRIGHT_HEADER_PATTERN.search(contents):
261        print "%s is missing a correct copyright header." % name
262        result = False
263    return result
264
265  def ProcessFiles(self, files, path):
266    success = True
267    for file in files:
268      try:
269        handle = open(file)
270        contents = handle.read()
271        success = self.ProcessContents(file, contents) and success
272      finally:
273        handle.close()
274    return success
275
276
277def GetOptions():
278  result = optparse.OptionParser()
279  result.add_option('--no-lint', help="Do not run cpplint", default=False,
280                    action="store_true")
281  return result
282
283
284def Main():
285  workspace = abspath(join(dirname(sys.argv[0]), '..'))
286  parser = GetOptions()
287  (options, args) = parser.parse_args()
288  success = True
289  if not options.no_lint:
290    success = CppLintProcessor().Run(workspace) and success
291  success = SourceProcessor().Run(workspace) and success
292  if success:
293    return 0
294  else:
295    return 1
296
297
298if __name__ == '__main__':
299  sys.exit(Main())
300