• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8Checks 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.
10Any source file including something not permitted by the DEPS files will fail.
11
12See builddeps.py for a detailed description of the DEPS format.
13"""
14
15import os
16import optparse
17import re
18import sys
19
20import cpp_checker
21import java_checker
22import results
23
24from builddeps import DepsBuilder
25from rules import Rule, Rules
26
27
28def _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
35class 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
143def 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
155Examples:
156  python checkdeps.py
157  python checkdeps.py --root c:\\source chrome"""
158
159
160def 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
228if '__main__' == __name__:
229  sys.exit(main())
230