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 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
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 | 58 import re |
59 import sys | 59 import sys |
60 import copy | 60 import copy |
61 | 61 |
62 import cpp_checker | |
63 import java_checker | |
64 | |
65 | |
62 # Variable name used in the DEPS file to add or subtract include files from | 66 # Variable name used in the DEPS file to add or subtract include files from |
63 # the module-level deps. | 67 # the module-level deps. |
64 INCLUDE_RULES_VAR_NAME = "include_rules" | 68 INCLUDE_RULES_VAR_NAME = "include_rules" |
65 | 69 |
66 # Optionally present in the DEPS file to list subdirectories which should not | 70 # 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. | 71 # be checked. This allows us to skip third party code, for example. |
68 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" | 72 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" |
69 | 73 |
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. | 74 # Set to true for more output. This is set by the command line options. |
78 VERBOSE = False | 75 VERBOSE = False |
79 | 76 |
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 | 77 # In lowercase, using forward slashes as directory separators, ending in a |
85 # forward slash. Set by the command line options. | 78 # forward slash. Set by the command line options. |
86 BASE_DIRECTORY = "" | 79 BASE_DIRECTORY = "" |
87 | 80 |
88 # The directories which contain the sources managed by git. | 81 # The directories which contain the sources managed by git. |
89 GIT_SOURCE_DIRECTORY = set() | 82 GIT_SOURCE_DIRECTORY = set() |
90 | 83 |
91 | 84 |
92 # Specifies a single rule for an include, which can be either allow or disallow. | 85 # Specifies a single rule for an include, which can be either allow or disallow. |
93 class Rule(object): | 86 class Rule(object): |
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
263 print " No deps file found in", dir_name | 256 print " No deps file found in", dir_name |
264 | 257 |
265 # Even if a DEPS file does not exist we still invoke ApplyRules | 258 # Even if a DEPS file does not exist we still invoke ApplyRules |
266 # to apply the implicit "allow" rule for the current directory | 259 # to apply the implicit "allow" rule for the current directory |
267 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) | 260 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) |
268 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) | 261 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) |
269 | 262 |
270 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs) | 263 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs) |
271 | 264 |
272 | 265 |
273 def ShouldCheckFile(file_name): | 266 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) | 267 (rules, skip_subdirs) = ApplyDirectoryRules(parent_rules, dir_name) |
379 if rules == None: | 268 if rules == None: |
380 return True | 269 return True |
381 | 270 |
382 # Collect a list of all files and directories to check. | 271 # Collect a list of all files and directories to check. |
383 files_to_check = [] | 272 files_to_check = [] |
384 dirs_to_check = [] | 273 dirs_to_check = [] |
385 success = True | 274 success = True |
386 contents = os.listdir(dir_name) | 275 contents = os.listdir(dir_name) |
387 for cur in contents: | 276 for cur in contents: |
388 if cur in skip_subdirs: | 277 if cur in skip_subdirs: |
389 continue # Don't check children that DEPS has asked us to skip. | 278 continue # Don't check children that DEPS has asked us to skip. |
390 full_name = os.path.join(dir_name, cur) | 279 full_name = os.path.join(dir_name, cur) |
391 if os.path.isdir(full_name): | 280 if os.path.isdir(full_name): |
392 dirs_to_check.append(full_name) | 281 dirs_to_check.append(full_name) |
393 elif ShouldCheckFile(full_name): | 282 elif os.path.splitext(full_name)[1] in checkers: |
394 files_to_check.append(full_name) | 283 files_to_check.append(full_name) |
395 | 284 |
396 # First check all files in this directory. | 285 # First check all files in this directory. |
397 for cur in files_to_check: | 286 for cur in files_to_check: |
398 file_status = CheckFile(rules, cur) | 287 checker = checkers[os.path.splitext(cur)[1]] |
399 if file_status != None: | 288 file_status = checker.CheckFile(rules, cur) |
289 if file_status: | |
400 print "ERROR in " + cur + "\n" + file_status | 290 print "ERROR in " + cur + "\n" + file_status |
401 success = False | 291 success = False |
402 | 292 |
403 # Next recurse into the subdirectories. | 293 # Next recurse into the subdirectories. |
404 for cur in dirs_to_check: | 294 for cur in dirs_to_check: |
405 if not CheckDirectory(rules, cur): | 295 if not CheckDirectory(rules, checkers, cur): |
406 success = False | 296 success = False |
407 | 297 |
408 return success | 298 return success |
409 | 299 |
410 | 300 |
411 def GetGitSourceDirectory(root): | 301 def GetGitSourceDirectory(root): |
412 """Returns a set of the directories to be checked. | 302 """Returns a set of the directories to be checked. |
413 | 303 |
414 Args: | 304 Args: |
415 root: The repository root where .git directory exists. | 305 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 | 371 # systems. Plus, we always use slashes here since the include parsing code |
482 # will also normalize to slashes. | 372 # will also normalize to slashes. |
483 BASE_DIRECTORY = BASE_DIRECTORY.lower() | 373 BASE_DIRECTORY = BASE_DIRECTORY.lower() |
484 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") | 374 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") |
485 start_dir = start_dir.replace("\\", "/") | 375 start_dir = start_dir.replace("\\", "/") |
486 | 376 |
487 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): | 377 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): |
488 global GIT_SOURCE_DIRECTORY | 378 global GIT_SOURCE_DIRECTORY |
489 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) | 379 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) |
490 | 380 |
491 success = CheckDirectory(base_rules, start_dir) | 381 java = java_checker.JavaChecker(BASE_DIRECTORY, VERBOSE) |
382 cpp = cpp_checker.CppChecker(VERBOSE) | |
383 checkers = dict((extension, checker) | |
M-A Ruel
2012/07/20 12:53:27
Weird alignment. Either all at +4 or at (. But I p
Iain Merrick
2012/07/23 14:22:09
Done.
| |
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 |