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 30try: 31 import hashlib 32 md5er = hashlib.md5 33except ImportError, e: 34 import md5 35 md5er = md5.new 36 37 38import optparse 39import os 40from os.path import abspath, join, dirname, basename, exists 41import pickle 42import re 43import sys 44import subprocess 45 46# Disabled LINT rules and reason. 47# build/include_what_you_use: Started giving false positives for variables 48# named "string" and "map" assuming that you needed to include STL headers. 49 50ENABLED_LINT_RULES = """ 51build/class 52build/deprecated 53build/endif_comment 54build/forward_decl 55build/include_order 56build/printf_format 57build/storage_class 58legal/copyright 59readability/boost 60readability/braces 61readability/casting 62readability/check 63readability/constructors 64readability/fn_size 65readability/function 66readability/multiline_comment 67readability/multiline_string 68readability/streams 69readability/todo 70readability/utf8 71runtime/arrays 72runtime/casting 73runtime/deprecated_fn 74runtime/explicit 75runtime/int 76runtime/memset 77runtime/mutex 78runtime/nonconf 79runtime/printf 80runtime/printf_format 81runtime/references 82runtime/rtti 83runtime/sizeof 84runtime/string 85runtime/virtual 86runtime/vlog 87whitespace/blank_line 88whitespace/braces 89whitespace/comma 90whitespace/comments 91whitespace/end_of_line 92whitespace/ending_newline 93whitespace/indent 94whitespace/labels 95whitespace/line_length 96whitespace/newline 97whitespace/operators 98whitespace/parens 99whitespace/tab 100whitespace/todo 101""".split() 102 103 104class 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 148class 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 179class 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 process = subprocess.Popen(command, stderr=subprocess.PIPE) 215 LINT_ERROR_PATTERN = re.compile(r'^(.+)[:(]\d+[:)]') 216 while True: 217 out_line = process.stderr.readline() 218 if out_line == '' and process.poll() != None: 219 break 220 sys.stderr.write(out_line) 221 m = LINT_ERROR_PATTERN.match(out_line) 222 if m: 223 good_files_cache.RemoveFile(m.group(1)) 224 225 good_files_cache.Save() 226 return process.returncode == 0 227 228 229COPYRIGHT_HEADER_PATTERN = re.compile( 230 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.') 231 232class SourceProcessor(SourceFileProcessor): 233 """ 234 Check that all files include a copyright notice. 235 """ 236 237 RELEVANT_EXTENSIONS = ['.js', '.cc', '.h', '.py', '.c', 'SConscript', 238 'SConstruct', '.status'] 239 def IsRelevant(self, name): 240 for ext in SourceProcessor.RELEVANT_EXTENSIONS: 241 if name.endswith(ext): 242 return True 243 return False 244 245 def GetPathsToSearch(self): 246 return ['.'] 247 248 def IgnoreDir(self, name): 249 return (super(SourceProcessor, self).IgnoreDir(name) 250 or (name == 'third_party') 251 or (name == 'obj')) 252 253 IGNORE_COPYRIGHTS = ['earley-boyer.js', 'raytrace.js', 'crypto.js', 254 'libraries.cc', 'libraries-empty.cc', 'jsmin.py', 'regexp-pcre.js'] 255 IGNORE_TABS = IGNORE_COPYRIGHTS + ['unicode-test.js', 256 'html-comments.js'] 257 258 def ProcessContents(self, name, contents): 259 result = True 260 base = basename(name) 261 if not base in SourceProcessor.IGNORE_TABS: 262 if '\t' in contents: 263 print "%s contains tabs" % name 264 result = False 265 if not base in SourceProcessor.IGNORE_COPYRIGHTS: 266 if not COPYRIGHT_HEADER_PATTERN.search(contents): 267 print "%s is missing a correct copyright header." % name 268 result = False 269 return result 270 271 def ProcessFiles(self, files, path): 272 success = True 273 for file in files: 274 try: 275 handle = open(file) 276 contents = handle.read() 277 success = self.ProcessContents(file, contents) and success 278 finally: 279 handle.close() 280 return success 281 282 283def GetOptions(): 284 result = optparse.OptionParser() 285 result.add_option('--no-lint', help="Do not run cpplint", default=False, 286 action="store_true") 287 return result 288 289 290def Main(): 291 workspace = abspath(join(dirname(sys.argv[0]), '..')) 292 parser = GetOptions() 293 (options, args) = parser.parse_args() 294 success = True 295 if not options.no_lint: 296 success = CppLintProcessor().Run(workspace) and success 297 success = SourceProcessor().Run(workspace) and success 298 if success: 299 return 0 300 else: 301 return 1 302 303 304if __name__ == '__main__': 305 sys.exit(Main()) 306