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 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 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 50 ENABLED_LINT_RULES = """ 51 build/class 52 build/deprecated 53 build/endif_comment 54 build/forward_decl 55 build/include_order 56 build/printf_format 57 build/storage_class 58 legal/copyright 59 readability/boost 60 readability/braces 61 readability/casting 62 readability/check 63 readability/constructors 64 readability/fn_size 65 readability/function 66 readability/multiline_comment 67 readability/multiline_string 68 readability/streams 69 readability/todo 70 readability/utf8 71 runtime/arrays 72 runtime/casting 73 runtime/deprecated_fn 74 runtime/explicit 75 runtime/int 76 runtime/memset 77 runtime/mutex 78 runtime/nonconf 79 runtime/printf 80 runtime/printf_format 81 runtime/references 82 runtime/rtti 83 runtime/sizeof 84 runtime/string 85 runtime/virtual 86 runtime/vlog 87 whitespace/blank_line 88 whitespace/braces 89 whitespace/comma 90 whitespace/comments 91 whitespace/end_of_line 92 whitespace/ending_newline 93 whitespace/indent 94 whitespace/labels 95 whitespace/line_length 96 whitespace/newline 97 whitespace/operators 98 whitespace/parens 99 whitespace/tab 100 whitespace/todo 101 """.split() 102 103 104 class 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 148 class 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 179 class 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 229 COPYRIGHT_HEADER_PATTERN = re.compile( 230 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.') 231 232 class 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 283 def 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 290 def 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 304 if __name__ == '__main__': 305 sys.exit(Main()) 306