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 31 import md5 32 import optparse 33 import os 34 from os.path import abspath, join, dirname, basename, exists 35 import pickle 36 import re 37 import sys 38 import 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 44 ENABLED_LINT_RULES = """ 45 build/class 46 build/deprecated 47 build/endif_comment 48 build/forward_decl 49 build/include_order 50 build/printf_format 51 build/storage_class 52 legal/copyright 53 readability/boost 54 readability/braces 55 readability/casting 56 readability/check 57 readability/constructors 58 readability/fn_size 59 readability/function 60 readability/multiline_comment 61 readability/multiline_string 62 readability/streams 63 readability/todo 64 readability/utf8 65 runtime/arrays 66 runtime/casting 67 runtime/deprecated_fn 68 runtime/explicit 69 runtime/int 70 runtime/memset 71 runtime/mutex 72 runtime/nonconf 73 runtime/printf 74 runtime/printf_format 75 runtime/references 76 runtime/rtti 77 runtime/sizeof 78 runtime/string 79 runtime/virtual 80 runtime/vlog 81 whitespace/blank_line 82 whitespace/braces 83 whitespace/comma 84 whitespace/comments 85 whitespace/end_of_line 86 whitespace/ending_newline 87 whitespace/indent 88 whitespace/labels 89 whitespace/line_length 90 whitespace/newline 91 whitespace/operators 92 whitespace/parens 93 whitespace/tab 94 whitespace/todo 95 """.split() 96 97 98 class 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 142 class 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 173 class 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 223 COPYRIGHT_HEADER_PATTERN = re.compile( 224 r'Copyright [\d-]*20[0-1][0-9] the V8 project authors. All rights reserved.') 225 226 class 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 277 def 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 284 def 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 298 if __name__ == '__main__': 299 sys.exit(Main()) 300