| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Makes sure that files include headers from allowed directories. | 6 """Makes sure that files include headers from allowed directories. |
| 7 | 7 |
| 8 Checks DEPS files in the source tree for rules, and applies those rules to | 8 Checks DEPS files in the source tree for rules, and applies those rules to |
| 9 "#include" commands in source files. Any source file including something not | 9 "#include" commands in source files. Any source file including something not |
| 10 permitted by the DEPS files will fail. | 10 permitted by the DEPS files will fail. |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 48 directory and then take away permissions from sub-parts, or the reverse. | 48 directory and then take away permissions from sub-parts, or the reverse. |
| 49 | 49 |
| 50 Note that all directory separators must be slashes (Unix-style) and not | 50 Note that all directory separators must be slashes (Unix-style) and not |
| 51 backslashes. All directories should be relative to the source root and use | 51 backslashes. All directories should be relative to the source root and use |
| 52 only lowercase. | 52 only lowercase. |
| 53 """ | 53 """ |
| 54 | 54 |
| 55 import os | 55 import os |
| 56 import optparse | 56 import optparse |
| 57 import pipes | 57 import pipes |
| 58 import re | |
| 59 import sys | 58 import sys |
| 60 import copy | 59 import copy |
| 61 | 60 |
| 61 import cpp_checker |
| 62 import java_checker |
| 63 |
| 64 |
| 62 # Variable name used in the DEPS file to add or subtract include files from | 65 # Variable name used in the DEPS file to add or subtract include files from |
| 63 # the module-level deps. | 66 # the module-level deps. |
| 64 INCLUDE_RULES_VAR_NAME = "include_rules" | 67 INCLUDE_RULES_VAR_NAME = "include_rules" |
| 65 | 68 |
| 66 # Optionally present in the DEPS file to list subdirectories which should not | 69 # Optionally present in the DEPS file to list subdirectories which should not |
| 67 # be checked. This allows us to skip third party code, for example. | 70 # be checked. This allows us to skip third party code, for example. |
| 68 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" | 71 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" |
| 69 | 72 |
| 70 # The maximum number of non-include lines we can see before giving up. | |
| 71 MAX_UNINTERESTING_LINES = 50 | |
| 72 | |
| 73 # The maximum line length, this is to be efficient in the case of very long | |
| 74 # lines (which can't be #includes). | |
| 75 MAX_LINE_LENGTH = 128 | |
| 76 | |
| 77 # Set to true for more output. This is set by the command line options. | 73 # Set to true for more output. This is set by the command line options. |
| 78 VERBOSE = False | 74 VERBOSE = False |
| 79 | 75 |
| 80 # This regular expression will be used to extract filenames from include | |
| 81 # statements. | |
| 82 EXTRACT_INCLUDE_PATH = re.compile('[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"') | |
| 83 | |
| 84 # In lowercase, using forward slashes as directory separators, ending in a | 76 # In lowercase, using forward slashes as directory separators, ending in a |
| 85 # forward slash. Set by the command line options. | 77 # forward slash. Set by the command line options. |
| 86 BASE_DIRECTORY = "" | 78 BASE_DIRECTORY = "" |
| 87 | 79 |
| 88 # The directories which contain the sources managed by git. | 80 # The directories which contain the sources managed by git. |
| 89 GIT_SOURCE_DIRECTORY = set() | 81 GIT_SOURCE_DIRECTORY = set() |
| 90 | 82 |
| 91 | 83 |
| 92 # Specifies a single rule for an include, which can be either allow or disallow. | 84 # Specifies a single rule for an include, which can be either allow or disallow. |
| 93 class Rule(object): | 85 class Rule(object): |
| 94 def __init__(self, allow, dir, source): | 86 def __init__(self, allow, directory, source): |
| 95 self._allow = allow | 87 self.allow = allow |
| 96 self._dir = dir | 88 self._dir = directory |
| 97 self._source = source | 89 self._source = source |
| 98 | 90 |
| 99 def __str__(self): | 91 def __str__(self): |
| 100 if (self._allow): | 92 if (self.allow): |
| 101 return '"+%s" from %s.' % (self._dir, self._source) | 93 return '"+%s" from %s.' % (self._dir, self._source) |
| 102 return '"-%s" from %s.' % (self._dir, self._source) | 94 return '"-%s" from %s.' % (self._dir, self._source) |
| 103 | 95 |
| 104 def ParentOrMatch(self, other): | 96 def ParentOrMatch(self, other): |
| 105 """Returns true if the input string is an exact match or is a parent | 97 """Returns true if the input string is an exact match or is a parent |
| 106 of the current rule. For example, the input "foo" would match "foo/bar".""" | 98 of the current rule. For example, the input "foo" would match "foo/bar".""" |
| 107 return self._dir == other or self._dir.startswith(other + "/") | 99 return self._dir == other or self._dir.startswith(other + "/") |
| 108 | 100 |
| 109 def ChildOrMatch(self, other): | 101 def ChildOrMatch(self, other): |
| 110 """Returns true if the input string would be covered by this rule. For | 102 """Returns true if the input string would be covered by this rule. For |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 153 self._rules = [x for x in self._rules if not x.ParentOrMatch(rule_dir)] | 145 self._rules = [x for x in self._rules if not x.ParentOrMatch(rule_dir)] |
| 154 self._rules.insert(0, Rule(add_rule, rule_dir, source)) | 146 self._rules.insert(0, Rule(add_rule, rule_dir, source)) |
| 155 | 147 |
| 156 def DirAllowed(self, allowed_dir): | 148 def DirAllowed(self, allowed_dir): |
| 157 """Returns a tuple (success, message), where success indicates if the given | 149 """Returns a tuple (success, message), where success indicates if the given |
| 158 directory is allowed given the current set of rules, and the message tells | 150 directory is allowed given the current set of rules, and the message tells |
| 159 why if the comparison failed.""" | 151 why if the comparison failed.""" |
| 160 for rule in self._rules: | 152 for rule in self._rules: |
| 161 if rule.ChildOrMatch(allowed_dir): | 153 if rule.ChildOrMatch(allowed_dir): |
| 162 # This rule applies. | 154 # This rule applies. |
| 163 if rule._allow: | 155 if rule.allow: |
| 164 return (True, "") | 156 return (True, "") |
| 165 return (False, rule.__str__()) | 157 return (False, rule.__str__()) |
| 166 # No rules apply, fail. | 158 # No rules apply, fail. |
| 167 return (False, "no rule applying") | 159 return (False, "no rule applying") |
| 168 | 160 |
| 169 | 161 |
| 170 def ApplyRules(existing_rules, includes, cur_dir): | 162 def ApplyRules(existing_rules, includes, cur_dir): |
| 171 """Applies the given include rules, returning the new rules. | 163 """Applies the given include rules, returning the new rules. |
| 172 | 164 |
| 173 Args: | 165 Args: |
| (...skipping 15 matching lines...) Expand all Loading... |
| 189 source = relative_dir | 181 source = relative_dir |
| 190 if len(source) == 0: | 182 if len(source) == 0: |
| 191 source = "top level" # Make the help string a little more meaningful. | 183 source = "top level" # Make the help string a little more meaningful. |
| 192 rules.AddRule("+" + relative_dir, "Default rule for " + source) | 184 rules.AddRule("+" + relative_dir, "Default rule for " + source) |
| 193 else: | 185 else: |
| 194 raise Exception("Internal error: base directory is not at the beginning" + | 186 raise Exception("Internal error: base directory is not at the beginning" + |
| 195 " for\n %s and base dir\n %s" % | 187 " for\n %s and base dir\n %s" % |
| 196 (cur_dir, BASE_DIRECTORY)) | 188 (cur_dir, BASE_DIRECTORY)) |
| 197 | 189 |
| 198 # Last, apply the additional explicit rules. | 190 # Last, apply the additional explicit rules. |
| 199 for (index, rule_str) in enumerate(includes): | 191 for (_, rule_str) in enumerate(includes): |
| 200 if not len(relative_dir): | 192 if not len(relative_dir): |
| 201 rule_description = "the top level include_rules" | 193 rule_description = "the top level include_rules" |
| 202 else: | 194 else: |
| 203 rule_description = relative_dir + "'s include_rules" | 195 rule_description = relative_dir + "'s include_rules" |
| 204 rules.AddRule(rule_str, rule_description) | 196 rules.AddRule(rule_str, rule_description) |
| 205 | 197 |
| 206 return rules | 198 return rules |
| 207 | 199 |
| 208 | 200 |
| 209 def ApplyDirectoryRules(existing_rules, dir_name): | 201 def ApplyDirectoryRules(existing_rules, dir_name): |
| (...skipping 16 matching lines...) Expand all Loading... |
| 226 # Check for a .svn directory in this directory or check this directory is | 218 # Check for a .svn directory in this directory or check this directory is |
| 227 # contained in git source direcotries. This will tell us if it's a source | 219 # contained in git source direcotries. This will tell us if it's a source |
| 228 # directory and should be checked. | 220 # directory and should be checked. |
| 229 if not (os.path.exists(os.path.join(dir_name, ".svn")) or | 221 if not (os.path.exists(os.path.join(dir_name, ".svn")) or |
| 230 (dir_name.lower() in GIT_SOURCE_DIRECTORY)): | 222 (dir_name.lower() in GIT_SOURCE_DIRECTORY)): |
| 231 return (None, []) | 223 return (None, []) |
| 232 | 224 |
| 233 # Check the DEPS file in this directory. | 225 # Check the DEPS file in this directory. |
| 234 if VERBOSE: | 226 if VERBOSE: |
| 235 print "Applying rules from", dir_name | 227 print "Applying rules from", dir_name |
| 236 def FromImpl(unused, unused2): | 228 def FromImpl(_unused, _unused2): |
| 237 pass # NOP function so "From" doesn't fail. | 229 pass # NOP function so "From" doesn't fail. |
| 238 | 230 |
| 239 def FileImpl(unused): | 231 def FileImpl(_unused): |
| 240 pass # NOP function so "File" doesn't fail. | 232 pass # NOP function so "File" doesn't fail. |
| 241 | 233 |
| 242 class _VarImpl: | 234 class _VarImpl: |
| 243 def __init__(self, local_scope): | 235 def __init__(self, local_scope): |
| 244 self._local_scope = local_scope | 236 self._local_scope = local_scope |
| 245 | 237 |
| 246 def Lookup(self, var_name): | 238 def Lookup(self, var_name): |
| 247 """Implements the Var syntax.""" | 239 """Implements the Var syntax.""" |
| 248 if var_name in self._local_scope.get("vars", {}): | 240 if var_name in self._local_scope.get("vars", {}): |
| 249 return self._local_scope["vars"][var_name] | 241 return self._local_scope["vars"][var_name] |
| 250 raise Error("Var is not defined: %s" % var_name) | 242 raise Exception("Var is not defined: %s" % var_name) |
| 251 | 243 |
| 252 local_scope = {} | 244 local_scope = {} |
| 253 global_scope = { | 245 global_scope = { |
| 254 "File": FileImpl, | 246 "File": FileImpl, |
| 255 "From": FromImpl, | 247 "From": FromImpl, |
| 256 "Var": _VarImpl(local_scope).Lookup, | 248 "Var": _VarImpl(local_scope).Lookup, |
| 257 } | 249 } |
| 258 deps_file = os.path.join(dir_name, "DEPS") | 250 deps_file = os.path.join(dir_name, "DEPS") |
| 259 | 251 |
| 260 if os.path.isfile(deps_file): | 252 if os.path.isfile(deps_file): |
| 261 execfile(deps_file, global_scope, local_scope) | 253 execfile(deps_file, global_scope, local_scope) |
| 262 elif VERBOSE: | 254 elif VERBOSE: |
| 263 print " No deps file found in", dir_name | 255 print " No deps file found in", dir_name |
| 264 | 256 |
| 265 # Even if a DEPS file does not exist we still invoke ApplyRules | 257 # Even if a DEPS file does not exist we still invoke ApplyRules |
| 266 # to apply the implicit "allow" rule for the current directory | 258 # to apply the implicit "allow" rule for the current directory |
| 267 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) | 259 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) |
| 268 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) | 260 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) |
| 269 | 261 |
| 270 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs) | 262 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs) |
| 271 | 263 |
| 272 | 264 |
| 273 def ShouldCheckFile(file_name): | 265 def CheckDirectory(parent_rules, checkers, dir_name): |
| 274 """Returns True if the given file is a type we want to check.""" | |
| 275 checked_extensions = [ | |
| 276 '.h', | |
| 277 '.cc', | |
| 278 '.m', | |
| 279 '.mm', | |
| 280 ] | |
| 281 basename, extension = os.path.splitext(file_name) | |
| 282 return extension in checked_extensions | |
| 283 | |
| 284 | |
| 285 def CheckLine(rules, line): | |
| 286 """Checks the given file with the given rule set. | |
| 287 Returns a tuple (is_include, illegal_description). | |
| 288 If the line is an #include directive the first value will be True. | |
| 289 If it is also an illegal include, the second value will be a string describing | |
| 290 the error. Otherwise, it will be None.""" | |
| 291 found_item = EXTRACT_INCLUDE_PATH.match(line) | |
| 292 if not found_item: | |
| 293 return False, None # Not a match | |
| 294 | |
| 295 include_path = found_item.group(1) | |
| 296 | |
| 297 # Fix up backslashes in case somebody accidentally used them. | |
| 298 include_path.replace("\\", "/") | |
| 299 | |
| 300 if include_path.find("/") < 0: | |
| 301 # Don't fail when no directory is specified. We may want to be more | |
| 302 # strict about this in the future. | |
| 303 if VERBOSE: | |
| 304 print " WARNING: directory specified with no path: " + include_path | |
| 305 return True, None | |
| 306 | |
| 307 (allowed, why_failed) = rules.DirAllowed(include_path) | |
| 308 if not allowed: | |
| 309 if VERBOSE: | |
| 310 retval = "\nFor " + rules.__str__() | |
| 311 else: | |
| 312 retval = "" | |
| 313 return True, retval + ('Illegal include: "%s"\n Because of %s' % | |
| 314 (include_path, why_failed)) | |
| 315 | |
| 316 return True, None | |
| 317 | |
| 318 | |
| 319 def CheckFile(rules, file_name): | |
| 320 """Checks the given file with the given rule set. | |
| 321 | |
| 322 Args: | |
| 323 rules: The set of rules that apply to files in this directory. | |
| 324 file_name: The source file to check. | |
| 325 | |
| 326 Returns: Either a string describing the error if there was one, or None if | |
| 327 the file checked out OK. | |
| 328 """ | |
| 329 if VERBOSE: | |
| 330 print "Checking: " + file_name | |
| 331 | |
| 332 ret_val = "" # We'll collect the error messages in here | |
| 333 last_include = 0 | |
| 334 try: | |
| 335 cur_file = open(file_name, "r") | |
| 336 in_if0 = 0 | |
| 337 for line_num in xrange(sys.maxint): | |
| 338 if line_num - last_include > MAX_UNINTERESTING_LINES: | |
| 339 break | |
| 340 | |
| 341 cur_line = cur_file.readline(MAX_LINE_LENGTH) | |
| 342 if cur_line == "": | |
| 343 break | |
| 344 cur_line = cur_line.strip() | |
| 345 | |
| 346 # Check to see if we're at / inside a #if 0 block | |
| 347 if cur_line == '#if 0': | |
| 348 in_if0 += 1 | |
| 349 continue | |
| 350 if in_if0 > 0: | |
| 351 if cur_line.startswith('#if'): | |
| 352 in_if0 += 1 | |
| 353 elif cur_line == '#endif': | |
| 354 in_if0 -= 1 | |
| 355 continue | |
| 356 | |
| 357 is_include, line_status = CheckLine(rules, cur_line) | |
| 358 if is_include: | |
| 359 last_include = line_num | |
| 360 if line_status is not None: | |
| 361 if len(line_status) > 0: # Add newline to separate messages. | |
| 362 line_status += "\n" | |
| 363 ret_val += line_status | |
| 364 cur_file.close() | |
| 365 | |
| 366 except IOError: | |
| 367 if VERBOSE: | |
| 368 print "Unable to open file: " + file_name | |
| 369 cur_file.close() | |
| 370 | |
| 371 # Map empty string to None for easier checking. | |
| 372 if len(ret_val) == 0: | |
| 373 return None | |
| 374 return ret_val | |
| 375 | |
| 376 | |
| 377 def CheckDirectory(parent_rules, dir_name): | |
| 378 (rules, skip_subdirs) = ApplyDirectoryRules(parent_rules, dir_name) | 266 (rules, skip_subdirs) = ApplyDirectoryRules(parent_rules, dir_name) |
| 379 if rules == None: | 267 if rules == None: |
| 380 return True | 268 return True |
| 381 | 269 |
| 382 # Collect a list of all files and directories to check. | 270 # Collect a list of all files and directories to check. |
| 383 files_to_check = [] | 271 files_to_check = [] |
| 384 dirs_to_check = [] | 272 dirs_to_check = [] |
| 385 success = True | 273 success = True |
| 386 contents = os.listdir(dir_name) | 274 contents = os.listdir(dir_name) |
| 387 for cur in contents: | 275 for cur in contents: |
| 388 if cur in skip_subdirs: | 276 if cur in skip_subdirs: |
| 389 continue # Don't check children that DEPS has asked us to skip. | 277 continue # Don't check children that DEPS has asked us to skip. |
| 390 full_name = os.path.join(dir_name, cur) | 278 full_name = os.path.join(dir_name, cur) |
| 391 if os.path.isdir(full_name): | 279 if os.path.isdir(full_name): |
| 392 dirs_to_check.append(full_name) | 280 dirs_to_check.append(full_name) |
| 393 elif ShouldCheckFile(full_name): | 281 elif os.path.splitext(full_name)[1] in checkers: |
| 394 files_to_check.append(full_name) | 282 files_to_check.append(full_name) |
| 395 | 283 |
| 396 # First check all files in this directory. | 284 # First check all files in this directory. |
| 397 for cur in files_to_check: | 285 for cur in files_to_check: |
| 398 file_status = CheckFile(rules, cur) | 286 checker = checkers[os.path.splitext(cur)[1]] |
| 399 if file_status != None: | 287 file_status = checker.CheckFile(rules, cur) |
| 288 if file_status: |
| 400 print "ERROR in " + cur + "\n" + file_status | 289 print "ERROR in " + cur + "\n" + file_status |
| 401 success = False | 290 success = False |
| 402 | 291 |
| 403 # Next recurse into the subdirectories. | 292 # Next recurse into the subdirectories. |
| 404 for cur in dirs_to_check: | 293 for cur in dirs_to_check: |
| 405 if not CheckDirectory(rules, cur): | 294 if not CheckDirectory(rules, checkers, cur): |
| 406 success = False | 295 success = False |
| 407 | 296 |
| 408 return success | 297 return success |
| 409 | 298 |
| 410 | 299 |
| 411 def GetGitSourceDirectory(root): | 300 def GetGitSourceDirectory(root): |
| 412 """Returns a set of the directories to be checked. | 301 """Returns a set of the directories to be checked. |
| 413 | 302 |
| 414 Args: | 303 Args: |
| 415 root: The repository root where .git directory exists. | 304 root: The repository root where .git directory exists. |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 481 # systems. Plus, we always use slashes here since the include parsing code | 370 # systems. Plus, we always use slashes here since the include parsing code |
| 482 # will also normalize to slashes. | 371 # will also normalize to slashes. |
| 483 BASE_DIRECTORY = BASE_DIRECTORY.lower() | 372 BASE_DIRECTORY = BASE_DIRECTORY.lower() |
| 484 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") | 373 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") |
| 485 start_dir = start_dir.replace("\\", "/") | 374 start_dir = start_dir.replace("\\", "/") |
| 486 | 375 |
| 487 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): | 376 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): |
| 488 global GIT_SOURCE_DIRECTORY | 377 global GIT_SOURCE_DIRECTORY |
| 489 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) | 378 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) |
| 490 | 379 |
| 491 success = CheckDirectory(base_rules, start_dir) | 380 java = java_checker.JavaChecker(BASE_DIRECTORY, VERBOSE) |
| 381 cpp = cpp_checker.CppChecker(VERBOSE) |
| 382 checkers = dict( |
| 383 (extension, checker) |
| 384 for checker in [java, cpp] for extension in checker.EXTENSIONS) |
| 385 success = CheckDirectory(base_rules, checkers, start_dir) |
| 492 if not success: | 386 if not success: |
| 493 print "\nFAILED\n" | 387 print "\nFAILED\n" |
| 494 return 1 | 388 return 1 |
| 495 print "\nSUCCESS\n" | 389 print "\nSUCCESS\n" |
| 496 return 0 | 390 return 0 |
| 497 | 391 |
| 498 | 392 |
| 499 def main(): | 393 def main(): |
| 500 option_parser = optparse.OptionParser() | 394 option_parser = optparse.OptionParser() |
| 501 option_parser.add_option("", "--root", default="", dest="base_directory", | 395 option_parser.add_option("", "--root", default="", dest="base_directory", |
| 502 help='Specifies the repository root. This defaults ' | 396 help='Specifies the repository root. This defaults ' |
| 503 'to "../../.." relative to the script file, which ' | 397 'to "../../.." relative to the script file, which ' |
| 504 'will normally be the repository root.') | 398 'will normally be the repository root.') |
| 505 option_parser.add_option("-v", "--verbose", action="store_true", | 399 option_parser.add_option("-v", "--verbose", action="store_true", |
| 506 default=False, help="Print debug logging") | 400 default=False, help="Print debug logging") |
| 507 options, args = option_parser.parse_args() | 401 options, args = option_parser.parse_args() |
| 508 return checkdeps(options, args) | 402 return checkdeps(options, args) |
| 509 | 403 |
| 510 | 404 |
| 511 if '__main__' == __name__: | 405 if '__main__' == __name__: |
| 512 sys.exit(main()) | 406 sys.exit(main()) |
| OLD | NEW |