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.
6 """Makes sure that files include headers from allowed directories.
8 Checks DEPS files in the source tree for rules, and applies those rules to
9 "#include" and "import" directives in the .cpp, .java, and .proto source files.
10 Any source file including something not permitted by the DEPS files will fail.
12 See README.md for a detailed description of the DEPS format.
25 from builddeps import DepsBuilder
26 from rules import Rule, Rules
29 def _IsTestFile(filename):
30 """Does a rudimentary check to try to skip test files; this could be
31 improved but is good enough for now.
33 return re.match('(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename)
36 class DepsChecker(DepsBuilder):
37 """Parses include_rules from DEPS files and verifies files in the
38 source tree against them.
46 ignore_temp_rules=False,
49 """Creates a new DepsChecker.
52 base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src.
53 verbose: Set to true for debug output.
54 being_tested: Set to true to ignore the DEPS file at tools/checkdeps/DEPS.
55 ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!").
58 self, base_directory, extra_repos, verbose, being_tested,
61 self._skip_tests = skip_tests
62 self._resolve_dotdot = resolve_dotdot
63 self.results_formatter = results.NormalResultsFormatter(verbose)
66 """Prints a report of results, and returns an exit code for the process."""
67 if self.results_formatter.GetResults():
68 self.results_formatter.PrintResults()
73 def CheckDirectory(self, start_dir):
74 """Checks all relevant source files in the specified directory and
75 its subdirectories for compliance with DEPS rules throughout the
76 tree (starting at |self.base_directory|). |start_dir| must be a
77 subdirectory of |self.base_directory|.
79 On completion, self.results_formatter has the results of
80 processing, and calling Report() will print a report of results.
82 java = java_checker.JavaChecker(self.base_directory, self.verbose)
83 cpp = cpp_checker.CppChecker(
84 self.verbose, self._resolve_dotdot, self.base_directory)
85 proto = proto_checker.ProtoChecker(
86 self.verbose, self._resolve_dotdot, self.base_directory)
89 for checker in [java, cpp, proto] for extension in checker.EXTENSIONS)
91 for rules, file_paths in self.GetAllRulesAndFiles(start_dir):
92 for full_name in file_paths:
93 if self._skip_tests and _IsTestFile(os.path.basename(full_name)):
95 file_extension = os.path.splitext(full_name)[1]
96 if not file_extension in checkers:
98 checker = checkers[file_extension]
99 file_status = checker.CheckFile(rules, full_name)
100 if file_status.HasViolations():
101 self.results_formatter.AddError(file_status)
103 def CheckIncludesAndImports(self, added_lines, checker):
104 """Check new import/#include statements added in the change
105 being presubmit checked.
108 added_lines: ((file_path, (changed_line, changed_line, ...), ...)
109 checker: CppChecker/JavaChecker/ProtoChecker checker instance
112 A list of tuples, (bad_file_path, rule_type, rule_description)
113 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
114 rule_description is human-readable. Empty if no problems.
117 for file_path, changed_lines in added_lines:
118 if not checker.ShouldCheck(file_path):
120 rules_for_file = self.GetDirectoryRules(os.path.dirname(file_path))
121 if not rules_for_file:
123 for line in changed_lines:
124 is_include, violation = checker.CheckLine(
125 rules_for_file, line, file_path, True)
128 rule_type = violation.violated_rule.allow
129 if rule_type == Rule.ALLOW:
131 violation_text = results.NormalResultsFormatter.FormatViolation(
132 violation, self.verbose)
133 problems.append((file_path, rule_type, violation_text))
136 def CheckAddedCppIncludes(self, added_includes):
137 """This is used from PRESUBMIT.py to check new #include statements added in
138 the change being presubmit checked.
141 added_includes: ((file_path, (include_line, include_line, ...), ...)
144 A list of tuples, (bad_file_path, rule_type, rule_description)
145 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
146 rule_description is human-readable. Empty if no problems.
148 return self.CheckIncludesAndImports(
149 added_includes, cpp_checker.CppChecker(self.verbose))
151 def CheckAddedJavaImports(self, added_imports, allow_multiple_definitions=None):
152 """This is used from PRESUBMIT.py to check new import statements added in
153 the change being presubmit checked.
156 added_imports: ((file_path, (import_line, import_line, ...), ...)
157 allow_multiple_definitions: [file_name, file_name, ...]. List of java file
158 names allowing multipe definition in presubmit check.
161 A list of tuples, (bad_file_path, rule_type, rule_description)
162 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
163 rule_description is human-readable. Empty if no problems.
165 return self.CheckIncludesAndImports(
167 java_checker.JavaChecker(self.base_directory, self.verbose,
168 added_imports, allow_multiple_definitions))
170 def CheckAddedProtoImports(self, added_imports):
171 """This is used from PRESUBMIT.py to check new #import statements added in
172 the change being presubmit checked.
175 added_imports : ((file_path, (import_line, import_line, ...), ...)
178 A list of tuples, (bad_file_path, rule_type, rule_description)
179 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and
180 rule_description is human-readable. Empty if no problems.
182 return self.CheckIncludesAndImports(
183 added_imports, proto_checker.ProtoChecker(
184 verbose=self.verbose, root_dir=self.base_directory))
187 print """Usage: python checkdeps.py [--root <root>] [tocheck]
189 --root ROOT Specifies the repository root. This defaults to "../../.."
190 relative to the script file. This will be correct given the
191 normal location of the script in "<root>/tools/checkdeps".
193 --(others) There are a few lesser-used options; run with --help to show them.
195 tocheck Specifies the directory, relative to root, to check. This defaults
196 to "." so it checks everything.
200 python checkdeps.py --root c:\\source chrome"""
204 option_parser = optparse.OptionParser()
205 option_parser.add_option(
207 default='', dest='base_directory',
208 help='Specifies the repository root. This defaults '
209 'to "../../.." relative to the script file, which '
210 'will normally be the repository root.')
211 option_parser.add_option(
213 action='append', dest='extra_repos', default=[],
214 help='Specifies extra repositories relative to root repository.')
215 option_parser.add_option(
216 '', '--ignore-temp-rules',
217 action='store_true', dest='ignore_temp_rules', default=False,
218 help='Ignore !-prefixed (temporary) rules.')
219 option_parser.add_option(
220 '', '--generate-temp-rules',
221 action='store_true', dest='generate_temp_rules', default=False,
222 help='Print rules to temporarily allow files that fail '
223 'dependency checking.')
224 option_parser.add_option(
225 '', '--count-violations',
226 action='store_true', dest='count_violations', default=False,
227 help='Count #includes in violation of intended rules.')
228 option_parser.add_option(
230 action='store_true', dest='skip_tests', default=False,
231 help='Skip checking test files (best effort).')
232 option_parser.add_option(
234 action='store_true', default=False,
235 help='Print debug logging')
236 option_parser.add_option(
238 help='Path to JSON output file')
239 option_parser.add_option(
240 '', '--no-resolve-dotdot',
241 action='store_false', dest='resolve_dotdot', default=True,
242 help='resolve leading ../ in include directive paths relative '
243 'to the file perfoming the inclusion.')
245 options, args = option_parser.parse_args()
247 deps_checker = DepsChecker(options.base_directory,
248 extra_repos=options.extra_repos,
249 verbose=options.verbose,
250 ignore_temp_rules=options.ignore_temp_rules,
251 skip_tests=options.skip_tests,
252 resolve_dotdot=options.resolve_dotdot)
253 base_directory = deps_checker.base_directory # Default if needed, normalized
255 # Figure out which directory we have to check.
256 start_dir = base_directory
258 # Directory specified. Start here. It's supposed to be relative to the
260 start_dir = os.path.abspath(os.path.join(base_directory, args[0]))
261 elif len(args) >= 2 or (options.generate_temp_rules and
262 options.count_violations):
263 # More than one argument, or incompatible flags, we don't handle this.
267 if not start_dir.startswith(deps_checker.base_directory):
268 print 'Directory to check must be a subdirectory of the base directory,'
269 print 'but %s is not a subdirectory of %s' % (start_dir, base_directory)
272 print 'Using base directory:', base_directory
273 print 'Checking:', start_dir
275 if options.generate_temp_rules:
276 deps_checker.results_formatter = results.TemporaryRulesFormatter()
277 elif options.count_violations:
278 deps_checker.results_formatter = results.CountViolationsFormatter()
281 deps_checker.results_formatter = results.JSONResultsFormatter(
282 options.json, deps_checker.results_formatter)
284 deps_checker.CheckDirectory(start_dir)
285 return deps_checker.Report()
288 if '__main__' == __name__: