OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """Makes sure that files include headers from allowed directories. |
| 7 |
| 8 Checks DEPS files in the source tree for rules, and applies those rules to |
| 9 "#include" and "import" directives in the .cpp and .java source files. |
| 10 Any source file including something not permitted by the DEPS files will fail. |
| 11 |
| 12 See builddeps.py for a detailed description of the DEPS format. |
| 13 """ |
| 14 |
| 15 import os |
| 16 import optparse |
| 17 import re |
| 18 import sys |
| 19 |
| 20 import cpp_checker |
| 21 import java_checker |
| 22 import results |
| 23 |
| 24 from builddeps import DepsBuilder |
| 25 from rules import Rule, Rules |
| 26 |
| 27 |
| 28 def _IsTestFile(filename): |
| 29 """Does a rudimentary check to try to skip test files; this could be |
| 30 improved but is good enough for now. |
| 31 """ |
| 32 return re.match('(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename) |
| 33 |
| 34 |
| 35 class DepsChecker(DepsBuilder): |
| 36 """Parses include_rules from DEPS files and verifies files in the |
| 37 source tree against them. |
| 38 """ |
| 39 |
| 40 def __init__(self, |
| 41 base_directory=None, |
| 42 verbose=False, |
| 43 being_tested=False, |
| 44 ignore_temp_rules=False, |
| 45 skip_tests=False): |
| 46 """Creates a new DepsChecker. |
| 47 |
| 48 Args: |
| 49 base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. |
| 50 verbose: Set to true for debug output. |
| 51 being_tested: Set to true to ignore the DEPS file at tools/checkdeps/DEPS. |
| 52 ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!"). |
| 53 """ |
| 54 DepsBuilder.__init__( |
| 55 self, base_directory, verbose, being_tested, ignore_temp_rules) |
| 56 |
| 57 self._skip_tests = skip_tests |
| 58 self.results_formatter = results.NormalResultsFormatter(verbose) |
| 59 |
| 60 def Report(self): |
| 61 """Prints a report of results, and returns an exit code for the process.""" |
| 62 if self.results_formatter.GetResults(): |
| 63 self.results_formatter.PrintResults() |
| 64 return 1 |
| 65 print '\nSUCCESS\n' |
| 66 return 0 |
| 67 |
| 68 def CheckDirectory(self, start_dir): |
| 69 """Checks all relevant source files in the specified directory and |
| 70 its subdirectories for compliance with DEPS rules throughout the |
| 71 tree (starting at |self.base_directory|). |start_dir| must be a |
| 72 subdirectory of |self.base_directory|. |
| 73 |
| 74 On completion, self.results_formatter has the results of |
| 75 processing, and calling Report() will print a report of results. |
| 76 """ |
| 77 java = java_checker.JavaChecker(self.base_directory, self.verbose) |
| 78 cpp = cpp_checker.CppChecker(self.verbose) |
| 79 checkers = dict( |
| 80 (extension, checker) |
| 81 for checker in [java, cpp] for extension in checker.EXTENSIONS) |
| 82 self._CheckDirectoryImpl(checkers, start_dir) |
| 83 |
| 84 def _CheckDirectoryImpl(self, checkers, dir_name): |
| 85 rules = self.GetDirectoryRules(dir_name) |
| 86 if rules is None: |
| 87 return |
| 88 |
| 89 # Collect a list of all files and directories to check. |
| 90 files_to_check = [] |
| 91 dirs_to_check = [] |
| 92 contents = sorted(os.listdir(dir_name)) |
| 93 for cur in contents: |
| 94 full_name = os.path.join(dir_name, cur) |
| 95 if os.path.isdir(full_name): |
| 96 dirs_to_check.append(full_name) |
| 97 elif os.path.splitext(full_name)[1] in checkers: |
| 98 if not self._skip_tests or not _IsTestFile(cur): |
| 99 files_to_check.append(full_name) |
| 100 |
| 101 # First check all files in this directory. |
| 102 for cur in files_to_check: |
| 103 checker = checkers[os.path.splitext(cur)[1]] |
| 104 file_status = checker.CheckFile(rules, cur) |
| 105 if file_status.HasViolations(): |
| 106 self.results_formatter.AddError(file_status) |
| 107 |
| 108 # Next recurse into the subdirectories. |
| 109 for cur in dirs_to_check: |
| 110 self._CheckDirectoryImpl(checkers, cur) |
| 111 |
| 112 def CheckAddedCppIncludes(self, added_includes): |
| 113 """This is used from PRESUBMIT.py to check new #include statements added in |
| 114 the change being presubmit checked. |
| 115 |
| 116 Args: |
| 117 added_includes: ((file_path, (include_line, include_line, ...), ...) |
| 118 |
| 119 Return: |
| 120 A list of tuples, (bad_file_path, rule_type, rule_description) |
| 121 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and |
| 122 rule_description is human-readable. Empty if no problems. |
| 123 """ |
| 124 cpp = cpp_checker.CppChecker(self.verbose) |
| 125 problems = [] |
| 126 for file_path, include_lines in added_includes: |
| 127 if not cpp.IsCppFile(file_path): |
| 128 continue |
| 129 rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path)) |
| 130 if not rules_for_file: |
| 131 continue |
| 132 for line in include_lines: |
| 133 is_include, violation = cpp.CheckLine( |
| 134 rules_for_file, line, file_path, True) |
| 135 if not violation: |
| 136 continue |
| 137 rule_type = violation.violated_rule.allow |
| 138 if rule_type == Rule.ALLOW: |
| 139 continue |
| 140 violation_text = results.NormalResultsFormatter.FormatViolation( |
| 141 violation, self.verbose) |
| 142 problems.append((file_path, rule_type, violation_text)) |
| 143 return problems |
| 144 |
| 145 |
| 146 def PrintUsage(): |
| 147 print """Usage: python checkdeps.py [--root <root>] [tocheck] |
| 148 |
| 149 --root ROOT Specifies the repository root. This defaults to "../../.." |
| 150 relative to the script file. This will be correct given the |
| 151 normal location of the script in "<root>/tools/checkdeps". |
| 152 |
| 153 --(others) There are a few lesser-used options; run with --help to show them. |
| 154 |
| 155 tocheck Specifies the directory, relative to root, to check. This defaults |
| 156 to "." so it checks everything. |
| 157 |
| 158 Examples: |
| 159 python checkdeps.py |
| 160 python checkdeps.py --root c:\\source chrome""" |
| 161 |
| 162 |
| 163 def main(): |
| 164 option_parser = optparse.OptionParser() |
| 165 option_parser.add_option( |
| 166 '', '--root', |
| 167 default='', dest='base_directory', |
| 168 help='Specifies the repository root. This defaults ' |
| 169 'to "../../.." relative to the script file, which ' |
| 170 'will normally be the repository root.') |
| 171 option_parser.add_option( |
| 172 '', '--ignore-temp-rules', |
| 173 action='store_true', dest='ignore_temp_rules', default=False, |
| 174 help='Ignore !-prefixed (temporary) rules.') |
| 175 option_parser.add_option( |
| 176 '', '--generate-temp-rules', |
| 177 action='store_true', dest='generate_temp_rules', default=False, |
| 178 help='Print rules to temporarily allow files that fail ' |
| 179 'dependency checking.') |
| 180 option_parser.add_option( |
| 181 '', '--count-violations', |
| 182 action='store_true', dest='count_violations', default=False, |
| 183 help='Count #includes in violation of intended rules.') |
| 184 option_parser.add_option( |
| 185 '', '--skip-tests', |
| 186 action='store_true', dest='skip_tests', default=False, |
| 187 help='Skip checking test files (best effort).') |
| 188 option_parser.add_option( |
| 189 '-v', '--verbose', |
| 190 action='store_true', default=False, |
| 191 help='Print debug logging') |
| 192 option_parser.add_option( |
| 193 '', '--json', |
| 194 help='Path to JSON output file') |
| 195 options, args = option_parser.parse_args() |
| 196 |
| 197 deps_checker = DepsChecker(options.base_directory, |
| 198 verbose=options.verbose, |
| 199 ignore_temp_rules=options.ignore_temp_rules, |
| 200 skip_tests=options.skip_tests) |
| 201 base_directory = deps_checker.base_directory # Default if needed, normalized |
| 202 |
| 203 # Figure out which directory we have to check. |
| 204 start_dir = base_directory |
| 205 if len(args) == 1: |
| 206 # Directory specified. Start here. It's supposed to be relative to the |
| 207 # base directory. |
| 208 start_dir = os.path.abspath(os.path.join(base_directory, args[0])) |
| 209 elif len(args) >= 2 or (options.generate_temp_rules and |
| 210 options.count_violations): |
| 211 # More than one argument, or incompatible flags, we don't handle this. |
| 212 PrintUsage() |
| 213 return 1 |
| 214 |
| 215 if not start_dir.startswith(deps_checker.base_directory): |
| 216 print 'Directory to check must be a subdirectory of the base directory,' |
| 217 print 'but %s is not a subdirectory of %s' % (start_dir, base_directory) |
| 218 return 1 |
| 219 |
| 220 print 'Using base directory:', base_directory |
| 221 print 'Checking:', start_dir |
| 222 |
| 223 if options.generate_temp_rules: |
| 224 deps_checker.results_formatter = results.TemporaryRulesFormatter() |
| 225 elif options.count_violations: |
| 226 deps_checker.results_formatter = results.CountViolationsFormatter() |
| 227 |
| 228 if options.json: |
| 229 deps_checker.results_formatter = results.JSONResultsFormatter( |
| 230 options.json, deps_checker.results_formatter) |
| 231 |
| 232 deps_checker.CheckDirectory(start_dir) |
| 233 return deps_checker.Report() |
| 234 |
| 235 |
| 236 if '__main__' == __name__: |
| 237 sys.exit(main()) |
OLD | NEW |