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
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.
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.
33 except ImportError, e:
40 from os.path import abspath, join, dirname, basename, exists
45 from subprocess import PIPE
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.
51 ENABLED_LINT_RULES = """
64 readability/constructors
67 readability/multiline_comment
68 readability/multiline_string
92 whitespace/ending_newline
95 whitespace/line_length
104 class FileContentsCache(object):
106 def __init__(self, sums_file_name):
108 self.sums_file_name = sums_file_name
114 sums_file = open(self.sums_file_name, 'r')
115 self.sums = pickle.load(sums_file)
117 # File might not exist, this is OK.
125 sums_file = open(self.sums_file_name, 'w')
126 pickle.dump(self.sums, sums_file)
130 def FilterUnchangedFiles(self, files):
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
141 return changed_or_new
143 def RemoveFile(self, file):
144 if file in self.sums:
148 class SourceFileProcessor(object):
150 Utility class that can run through a directory structure, find all relevant
151 files and invoke a custom check on the files.
156 for file in self.GetPathsToSearch():
157 all_files += self.FindFilesIn(join(path, file))
158 if not self.ProcessFiles(all_files, path):
162 def IgnoreDir(self, name):
163 return name.startswith('.') or name == 'data' or name == 'sputniktests'
165 def IgnoreFile(self, name):
166 return name.startswith('.')
168 def FindFilesIn(self, path):
170 for (root, dirs, files) in os.walk(path):
171 for ignored in [x for x in dirs if self.IgnoreDir(x)]:
174 if not self.IgnoreFile(file) and self.IsRelevant(file):
175 result.append(join(root, file))
179 class CppLintProcessor(SourceFileProcessor):
181 Lint files to check that they follow the google code style.
184 def IsRelevant(self, name):
185 return name.endswith('.cc') or name.endswith('.h')
187 def IgnoreDir(self, name):
188 return (super(CppLintProcessor, self).IgnoreDir(name)
189 or (name == 'third_party'))
191 IGNORE_LINT = ['flag-definitions.h']
193 def IgnoreFile(self, name):
194 return (super(CppLintProcessor, self).IgnoreFile(name)
195 or (name in CppLintProcessor.IGNORE_LINT))
197 def GetPathsToSearch(self):
198 return ['src', 'preparser', 'include', 'samples', join('test', 'cctest')]
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)
205 print 'No changes in files detected. Skipping cpplint check.'
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)
215 process = subprocess.Popen(command, stderr=subprocess.PIPE)
217 print('Error running cpplint.py. Please make sure you have depot_tools' +
218 ' in your $PATH. Lint check skipped.')
220 LINT_ERROR_PATTERN = re.compile(r'^(.+)[:(]\d+[:)]')
222 out_line = process.stderr.readline()
223 if out_line == '' and process.poll() != None:
225 sys.stderr.write(out_line)
226 m = LINT_ERROR_PATTERN.match(out_line)
228 good_files_cache.RemoveFile(m.group(1))
230 good_files_cache.Save()
231 return process.returncode == 0
234 COPYRIGHT_HEADER_PATTERN = re.compile(
235 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.')
237 class SourceProcessor(SourceFileProcessor):
239 Check that all files include a copyright notice and no trailing whitespaces.
242 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c', 'SConscript',
243 'SConstruct', '.status', '.gyp', '.gypi']
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)
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):
256 if self.IsRelevant(file) and not self.IgnoreFile(file):
257 result.append(join(path, file))
258 if output.wait() == 0:
260 return super(SourceProcessor, self).FindFilesIn(path)
262 def IsRelevant(self, name):
263 for ext in SourceProcessor.RELEVANT_EXTENSIONS:
264 if name.endswith(ext):
268 def GetPathsToSearch(self):
271 def IgnoreDir(self, name):
272 return (super(SourceProcessor, self).IgnoreDir(name)
273 or (name == 'third_party')
278 IGNORE_COPYRIGHTS = ['cpplint.py',
283 'libraries-empty.cc',
286 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 'html-comments.js']
288 def ProcessContents(self, name, contents):
290 base = basename(name)
291 if not base in SourceProcessor.IGNORE_TABS:
293 print "%s contains tabs" % name
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
299 ext = base.split('.').pop()
300 if ' \n' in contents or contents.endswith(' '):
303 parts = contents.split(' \n')
304 if not contents.endswith(' '):
307 line += part.count('\n') + 1
308 lines.append(str(line))
309 linenumbers = ', '.join(lines)
311 print "%s has trailing whitespaces in lines %s." % (name, linenumbers)
313 print "%s has trailing whitespaces in line %s." % (name, linenumbers)
317 def ProcessFiles(self, files, path):
323 contents = handle.read()
324 if not self.ProcessContents(file, contents):
329 print "Total violating files: %s" % violations
334 result = optparse.OptionParser()
335 result.add_option('--no-lint', help="Do not run cpplint", default=False,
341 workspace = abspath(join(dirname(sys.argv[0]), '..'))
342 parser = GetOptions()
343 (options, args) = parser.parse_args()
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
356 if __name__ == '__main__':