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 erifies 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 == 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 = 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 pass 129 rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path)) 130 if rules_for_file: 131 for line in include_lines: 132 is_include, violation = cpp.CheckLine( 133 rules_for_file, line, file_path, True) 134 if violation: 135 rule_type = violation.violated_rule.allow 136 if rule_type != Rule.ALLOW: 137 violation_text = results.NormalResultsFormatter.FormatViolation( 138 violation, self.verbose) 139 problems.append((file_path, rule_type, violation_text)) 140 return problems 141 142 143 def PrintUsage(): 144 print """Usage: python checkdeps.py [--root <root>] [tocheck] 145 146 --root ROOT Specifies the repository root. This defaults to "../../.." 147 relative to the script file. This will be correct given the 148 normal location of the script in "<root>/tools/checkdeps". 149 150 --(others) There are a few lesser-used options; run with --help to show them. 151 152 tocheck Specifies the directory, relative to root, to check. This defaults 153 to "." so it checks everything. 154 155 Examples: 156 python checkdeps.py 157 python checkdeps.py --root c:\\source chrome""" 158 159 160 def main(): 161 option_parser = optparse.OptionParser() 162 option_parser.add_option( 163 '', '--root', 164 default='', dest='base_directory', 165 help='Specifies the repository root. This defaults ' 166 'to "../../.." relative to the script file, which ' 167 'will normally be the repository root.') 168 option_parser.add_option( 169 '', '--ignore-temp-rules', 170 action='store_true', dest='ignore_temp_rules', default=False, 171 help='Ignore !-prefixed (temporary) rules.') 172 option_parser.add_option( 173 '', '--generate-temp-rules', 174 action='store_true', dest='generate_temp_rules', default=False, 175 help='Print rules to temporarily allow files that fail ' 176 'dependency checking.') 177 option_parser.add_option( 178 '', '--count-violations', 179 action='store_true', dest='count_violations', default=False, 180 help='Count #includes in violation of intended rules.') 181 option_parser.add_option( 182 '', '--skip-tests', 183 action='store_true', dest='skip_tests', default=False, 184 help='Skip checking test files (best effort).') 185 option_parser.add_option( 186 '-v', '--verbose', 187 action='store_true', default=False, 188 help='Print debug logging') 189 option_parser.add_option( 190 '', '--json', 191 help='Path to JSON output file') 192 options, args = option_parser.parse_args() 193 194 deps_checker = DepsChecker(options.base_directory, 195 verbose=options.verbose, 196 ignore_temp_rules=options.ignore_temp_rules, 197 skip_tests=options.skip_tests) 198 199 # Figure out which directory we have to check. 200 start_dir = deps_checker.base_directory 201 if len(args) == 1: 202 # Directory specified. Start here. It's supposed to be relative to the 203 # base directory. 204 start_dir = os.path.abspath( 205 os.path.join(deps_checker.base_directory, args[0])) 206 elif len(args) >= 2 or (options.generate_temp_rules and 207 options.count_violations): 208 # More than one argument, or incompatible flags, we don't handle this. 209 PrintUsage() 210 return 1 211 212 print 'Using base directory:', deps_checker.base_directory 213 print 'Checking:', start_dir 214 215 if options.generate_temp_rules: 216 deps_checker.results_formatter = results.TemporaryRulesFormatter() 217 elif options.count_violations: 218 deps_checker.results_formatter = results.CountViolationsFormatter() 219 220 if options.json: 221 deps_checker.results_formatter = results.JSONResultsFormatter( 222 options.json, deps_checker.results_formatter) 223 224 deps_checker.CheckDirectory(start_dir) 225 return deps_checker.Report() 226 227 228 if '__main__' == __name__: 229 sys.exit(main()) 230