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 and .java source files.
10 Any source file including something not permitted by the DEPS files will fail.
12 See builddeps.py for a detailed description of the DEPS format.
24 from builddeps
import DepsBuilder
25 from rules
import Rule
, Rules
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.
32 return re
.match('(test|mock|dummy)_.*|.*_[a-z]*test\.(cc|mm|java)', filename
)
35 class DepsChecker(DepsBuilder
):
36 """Parses include_rules from DEPS files and verifies files in the
37 source tree against them.
44 ignore_temp_rules
=False,
46 """Creates a new DepsChecker.
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 ("!").
55 self
, base_directory
, verbose
, being_tested
, ignore_temp_rules
)
57 self
._skip
_tests
= skip_tests
58 self
.results_formatter
= results
.NormalResultsFormatter(verbose
)
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()
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|.
74 On completion, self.results_formatter has the results of
75 processing, and calling Report() will print a report of results.
77 java
= java_checker
.JavaChecker(self
.base_directory
, self
.verbose
)
78 cpp
= cpp_checker
.CppChecker(self
.verbose
)
81 for checker
in [java
, cpp
] for extension
in checker
.EXTENSIONS
)
82 self
._CheckDirectoryImpl
(checkers
, start_dir
)
84 def _CheckDirectoryImpl(self
, checkers
, dir_name
):
85 rules
= self
.GetDirectoryRules(dir_name
)
89 # Collect a list of all files and directories to check.
92 contents
= sorted(os
.listdir(dir_name
))
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
)
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
)
108 # Next recurse into the subdirectories.
109 for cur
in dirs_to_check
:
110 self
._CheckDirectoryImpl
(checkers
, cur
)
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.
117 added_includes: ((file_path, (include_line, include_line, ...), ...)
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.
124 cpp
= cpp_checker
.CppChecker(self
.verbose
)
126 for file_path
, include_lines
in added_includes
:
127 if not cpp
.IsCppFile(file_path
):
129 rules_for_file
= self
.GetDirectoryRules(os
.path
.dirname(file_path
))
130 if not rules_for_file
:
132 for line
in include_lines
:
133 is_include
, violation
= cpp
.CheckLine(
134 rules_for_file
, line
, file_path
, True)
137 rule_type
= violation
.violated_rule
.allow
138 if rule_type
== Rule
.ALLOW
:
140 violation_text
= results
.NormalResultsFormatter
.FormatViolation(
141 violation
, self
.verbose
)
142 problems
.append((file_path
, rule_type
, violation_text
))
147 print """Usage: python checkdeps.py [--root <root>] [tocheck]
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".
153 --(others) There are a few lesser-used options; run with --help to show them.
155 tocheck Specifies the directory, relative to root, to check. This defaults
156 to "." so it checks everything.
160 python checkdeps.py --root c:\\source chrome"""
164 option_parser
= optparse
.OptionParser()
165 option_parser
.add_option(
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(
186 action
='store_true', dest
='skip_tests', default
=False,
187 help='Skip checking test files (best effort).')
188 option_parser
.add_option(
190 action
='store_true', default
=False,
191 help='Print debug logging')
192 option_parser
.add_option(
194 help='Path to JSON output file')
195 options
, args
= option_parser
.parse_args()
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
203 # Figure out which directory we have to check.
204 start_dir
= base_directory
206 # Directory specified. Start here. It's supposed to be relative to the
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.
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
)
220 print 'Using base directory:', base_directory
221 print 'Checking:', start_dir
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()
229 deps_checker
.results_formatter
= results
.JSONResultsFormatter(
230 options
.json
, deps_checker
.results_formatter
)
232 deps_checker
.CheckDirectory(start_dir
)
233 return deps_checker
.Report()
236 if '__main__' == __name__
: