blob: 3f27c001a1365c38311b101699d23bac31e9f701 [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001#!/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
Steve Blockd0582a62009-12-15 09:54:21 +000031import md5
Steve Blocka7e24c12009-10-30 11:49:00 +000032import optparse
33import os
34from os.path import abspath, join, dirname, basename, exists
Steve Blockd0582a62009-12-15 09:54:21 +000035import pickle
Steve Blocka7e24c12009-10-30 11:49:00 +000036import 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
Steve Blockd0582a62009-12-15 09:54:21 +000098class 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
Steve Blocka7e24c12009-10-30 11:49:00 +0000142class 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):
Steve Blockd0582a62009-12-15 09:54:21 +0000157 return name.startswith('.') or name == 'data' or name == 'sputniktests'
Steve Blocka7e24c12009-10-30 11:49:00 +0000158
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']
Steve Blockd0582a62009-12-15 09:54:21 +0000186
Steve Blocka7e24c12009-10-30 11:49:00 +0000187 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):
Steve Blockd0582a62009-12-15 09:54:21 +0000195 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
Steve Blocka7e24c12009-10-30 11:49:00 +0000202 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)
Steve Blockd0582a62009-12-15 09:54:21 +0000207
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
Steve Blocka7e24c12009-10-30 11:49:00 +0000221
222
223COPYRIGHT_HEADER_PATTERN = re.compile(
224 r'Copyright [\d-]*200[8-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())