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