checkdeps.py 11 KB

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