|  | #!/usr/bin/env python | 
|  | # Copyright 2012 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | """Makes sure that files include headers from allowed directories. | 
|  |  | 
|  | Checks DEPS files in the source tree for rules, and applies those rules to | 
|  | "#include" and "import" directives in the .cpp, .java, and .proto source files. | 
|  | Any source file including something not permitted by the DEPS files will fail. | 
|  |  | 
|  | See README.md for a detailed description of the DEPS format. | 
|  | """ | 
|  |  | 
|  | import os | 
|  | import optparse | 
|  | import re | 
|  | import sys | 
|  |  | 
|  | import proto_checker | 
|  | import cpp_checker | 
|  | import java_checker | 
|  | import results | 
|  |  | 
|  | from builddeps import DepsBuilder | 
|  | from rules import Rule, Rules | 
|  |  | 
|  |  | 
|  | def _IsTestFile(filename): | 
|  | """Does a rudimentary check to try to skip test files; this could be | 
|  | improved but is good enough for now. | 
|  | """ | 
|  | return re.match('(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename) | 
|  |  | 
|  |  | 
|  | class DepsChecker(DepsBuilder): | 
|  | """Parses include_rules from DEPS files and verifies files in the | 
|  | source tree against them. | 
|  | """ | 
|  |  | 
|  | def __init__(self, | 
|  | base_directory=None, | 
|  | extra_repos=[], | 
|  | verbose=False, | 
|  | being_tested=False, | 
|  | ignore_temp_rules=False, | 
|  | skip_tests=False, | 
|  | resolve_dotdot=True): | 
|  | """Creates a new DepsChecker. | 
|  |  | 
|  | Args: | 
|  | base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. | 
|  | verbose: Set to true for debug output. | 
|  | being_tested: Set to true to ignore the DEPS file at tools/checkdeps/DEPS. | 
|  | ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!"). | 
|  | """ | 
|  | DepsBuilder.__init__( | 
|  | self, base_directory, extra_repos, verbose, being_tested, | 
|  | ignore_temp_rules) | 
|  |  | 
|  | self._skip_tests = skip_tests | 
|  | self._resolve_dotdot = resolve_dotdot | 
|  | self.results_formatter = results.NormalResultsFormatter(verbose) | 
|  |  | 
|  | def Report(self): | 
|  | """Prints a report of results, and returns an exit code for the process.""" | 
|  | if self.results_formatter.GetResults(): | 
|  | self.results_formatter.PrintResults() | 
|  | return 1 | 
|  | print '\nSUCCESS\n' | 
|  | return 0 | 
|  |  | 
|  | def CheckDirectory(self, start_dir): | 
|  | """Checks all relevant source files in the specified directory and | 
|  | its subdirectories for compliance with DEPS rules throughout the | 
|  | tree (starting at |self.base_directory|).  |start_dir| must be a | 
|  | subdirectory of |self.base_directory|. | 
|  |  | 
|  | On completion, self.results_formatter has the results of | 
|  | processing, and calling Report() will print a report of results. | 
|  | """ | 
|  | java = java_checker.JavaChecker(self.base_directory, self.verbose) | 
|  | cpp = cpp_checker.CppChecker( | 
|  | self.verbose, self._resolve_dotdot, self.base_directory) | 
|  | proto = proto_checker.ProtoChecker( | 
|  | self.verbose, self._resolve_dotdot, self.base_directory) | 
|  | checkers = dict( | 
|  | (extension, checker) | 
|  | for checker in [java, cpp, proto] for extension in checker.EXTENSIONS) | 
|  |  | 
|  | for rules, file_paths in self.GetAllRulesAndFiles(start_dir): | 
|  | for full_name in file_paths: | 
|  | if self._skip_tests and _IsTestFile(os.path.basename(full_name)): | 
|  | continue | 
|  | file_extension = os.path.splitext(full_name)[1] | 
|  | if not file_extension in checkers: | 
|  | continue | 
|  | checker = checkers[file_extension] | 
|  | file_status = checker.CheckFile(rules, full_name) | 
|  | if file_status.HasViolations(): | 
|  | self.results_formatter.AddError(file_status) | 
|  |  | 
|  | def CheckIncludesAndImports(self, added_lines, checker): | 
|  | """Check new import/#include statements added in the change | 
|  | being presubmit checked. | 
|  |  | 
|  | Args: | 
|  | added_lines: ((file_path, (changed_line, changed_line, ...), ...) | 
|  | checker: CppChecker/JavaChecker/ProtoChecker checker instance | 
|  |  | 
|  | Return: | 
|  | A list of tuples, (bad_file_path, rule_type, rule_description) | 
|  | where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and | 
|  | rule_description is human-readable. Empty if no problems. | 
|  | """ | 
|  | problems = [] | 
|  | for file_path, changed_lines in added_lines: | 
|  | if not checker.ShouldCheck(file_path): | 
|  | continue | 
|  | rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path)) | 
|  | if not rules_for_file: | 
|  | continue | 
|  | for line in changed_lines: | 
|  | is_include, violation = checker.CheckLine( | 
|  | rules_for_file, line, file_path, True) | 
|  | if not violation: | 
|  | continue | 
|  | rule_type = violation.violated_rule.allow | 
|  | if rule_type == Rule.ALLOW: | 
|  | continue | 
|  | violation_text = results.NormalResultsFormatter.FormatViolation( | 
|  | violation, self.verbose) | 
|  | problems.append((file_path, rule_type, violation_text)) | 
|  | return problems | 
|  |  | 
|  | def CheckAddedCppIncludes(self, added_includes): | 
|  | """This is used from PRESUBMIT.py to check new #include statements added in | 
|  | the change being presubmit checked. | 
|  |  | 
|  | Args: | 
|  | added_includes: ((file_path, (include_line, include_line, ...), ...) | 
|  |  | 
|  | Return: | 
|  | A list of tuples, (bad_file_path, rule_type, rule_description) | 
|  | where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and | 
|  | rule_description is human-readable. Empty if no problems. | 
|  | """ | 
|  | return self.CheckIncludesAndImports( | 
|  | added_includes, cpp_checker.CppChecker(self.verbose)) | 
|  |  | 
|  | def CheckAddedJavaImports(self, added_imports, allow_multiple_definitions=None): | 
|  | """This is used from PRESUBMIT.py to check new import statements added in | 
|  | the change being presubmit checked. | 
|  |  | 
|  | Args: | 
|  | added_imports: ((file_path, (import_line, import_line, ...), ...) | 
|  | allow_multiple_definitions: [file_name, file_name, ...]. List of java file | 
|  | names allowing multipe definition in presubmit check. | 
|  |  | 
|  | Return: | 
|  | A list of tuples, (bad_file_path, rule_type, rule_description) | 
|  | where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and | 
|  | rule_description is human-readable. Empty if no problems. | 
|  | """ | 
|  | return self.CheckIncludesAndImports( | 
|  | added_imports, | 
|  | java_checker.JavaChecker(self.base_directory, self.verbose, | 
|  | added_imports, allow_multiple_definitions)) | 
|  |  | 
|  | def CheckAddedProtoImports(self, added_imports): | 
|  | """This is used from PRESUBMIT.py to check new #import statements added in | 
|  | the change being presubmit checked. | 
|  |  | 
|  | Args: | 
|  | added_imports : ((file_path, (import_line, import_line, ...), ...) | 
|  |  | 
|  | Return: | 
|  | A list of tuples, (bad_file_path, rule_type, rule_description) | 
|  | where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and | 
|  | rule_description is human-readable. Empty if no problems. | 
|  | """ | 
|  | return self.CheckIncludesAndImports( | 
|  | added_imports, proto_checker.ProtoChecker( | 
|  | verbose=self.verbose, root_dir=self.base_directory)) | 
|  |  | 
|  | def PrintUsage(): | 
|  | print """Usage: python checkdeps.py [--root <root>] [tocheck] | 
|  |  | 
|  | --root ROOT Specifies the repository root. This defaults to "../../.." | 
|  | relative to the script file. This will be correct given the | 
|  | normal location of the script in "<root>/tools/checkdeps". | 
|  |  | 
|  | --(others)  There are a few lesser-used options; run with --help to show them. | 
|  |  | 
|  | tocheck  Specifies the directory, relative to root, to check. This defaults | 
|  | to "." so it checks everything. | 
|  |  | 
|  | Examples: | 
|  | python checkdeps.py | 
|  | python checkdeps.py --root c:\\source chrome""" | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | option_parser = optparse.OptionParser() | 
|  | option_parser.add_option( | 
|  | '', '--root', | 
|  | default='', dest='base_directory', | 
|  | help='Specifies the repository root. This defaults ' | 
|  | 'to "../../.." relative to the script file, which ' | 
|  | 'will normally be the repository root.') | 
|  | option_parser.add_option( | 
|  | '', '--extra-repos', | 
|  | action='append', dest='extra_repos', default=[], | 
|  | help='Specifies extra repositories relative to root repository.') | 
|  | option_parser.add_option( | 
|  | '', '--ignore-temp-rules', | 
|  | action='store_true', dest='ignore_temp_rules', default=False, | 
|  | help='Ignore !-prefixed (temporary) rules.') | 
|  | option_parser.add_option( | 
|  | '', '--generate-temp-rules', | 
|  | action='store_true', dest='generate_temp_rules', default=False, | 
|  | help='Print rules to temporarily allow files that fail ' | 
|  | 'dependency checking.') | 
|  | option_parser.add_option( | 
|  | '', '--count-violations', | 
|  | action='store_true', dest='count_violations', default=False, | 
|  | help='Count #includes in violation of intended rules.') | 
|  | option_parser.add_option( | 
|  | '', '--skip-tests', | 
|  | action='store_true', dest='skip_tests', default=False, | 
|  | help='Skip checking test files (best effort).') | 
|  | option_parser.add_option( | 
|  | '-v', '--verbose', | 
|  | action='store_true', default=False, | 
|  | help='Print debug logging') | 
|  | option_parser.add_option( | 
|  | '', '--json', | 
|  | help='Path to JSON output file') | 
|  | option_parser.add_option( | 
|  | '', '--no-resolve-dotdot', | 
|  | action='store_false', dest='resolve_dotdot', default=True, | 
|  | help='resolve leading ../ in include directive paths relative ' | 
|  | 'to the file perfoming the inclusion.') | 
|  |  | 
|  | options, args = option_parser.parse_args() | 
|  |  | 
|  | deps_checker = DepsChecker(options.base_directory, | 
|  | extra_repos=options.extra_repos, | 
|  | verbose=options.verbose, | 
|  | ignore_temp_rules=options.ignore_temp_rules, | 
|  | skip_tests=options.skip_tests, | 
|  | resolve_dotdot=options.resolve_dotdot) | 
|  | base_directory = deps_checker.base_directory  # Default if needed, normalized | 
|  |  | 
|  | # Figure out which directory we have to check. | 
|  | start_dir = base_directory | 
|  | if len(args) == 1: | 
|  | # Directory specified. Start here. It's supposed to be relative to the | 
|  | # base directory. | 
|  | start_dir = os.path.abspath(os.path.join(base_directory, args[0])) | 
|  | elif len(args) >= 2 or (options.generate_temp_rules and | 
|  | options.count_violations): | 
|  | # More than one argument, or incompatible flags, we don't handle this. | 
|  | PrintUsage() | 
|  | return 1 | 
|  |  | 
|  | if not start_dir.startswith(deps_checker.base_directory): | 
|  | print 'Directory to check must be a subdirectory of the base directory,' | 
|  | print 'but %s is not a subdirectory of %s' % (start_dir, base_directory) | 
|  | return 1 | 
|  |  | 
|  | print 'Using base directory:', base_directory | 
|  | print 'Checking:', start_dir | 
|  |  | 
|  | if options.generate_temp_rules: | 
|  | deps_checker.results_formatter = results.TemporaryRulesFormatter() | 
|  | elif options.count_violations: | 
|  | deps_checker.results_formatter = results.CountViolationsFormatter() | 
|  |  | 
|  | if options.json: | 
|  | deps_checker.results_formatter = results.JSONResultsFormatter( | 
|  | options.json, deps_checker.results_formatter) | 
|  |  | 
|  | deps_checker.CheckDirectory(start_dir) | 
|  | return deps_checker.Report() | 
|  |  | 
|  |  | 
|  | if '__main__' == __name__: | 
|  | sys.exit(main()) |