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 |