OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Makes sure that files include headers from allowed directories. | 6 """Makes sure that files include headers from allowed directories. |
7 | 7 |
8 Checks DEPS files in the source tree for rules, and applies those rules to | 8 Checks DEPS files in the source tree for rules, and applies those rules to |
9 "#include" commands in source files. Any source file including something not | 9 "#include" commands in source files. Any source file including something not |
10 permitted by the DEPS files will fail. | 10 permitted by the DEPS files will fail. |
11 | 11 |
12 The format of the deps file: | 12 The format of the deps file: |
13 | 13 |
14 First you have the normal module-level deps. These are the ones used by | 14 First you have the normal module-level deps. These are the ones used by |
15 gclient. An example would be: | 15 gclient. An example would be: |
16 | 16 |
17 deps = { | 17 deps = { |
18 "base":"http://foo.bar/trunk/base" | 18 "base":"http://foo.bar/trunk/base" |
19 } | 19 } |
20 | 20 |
21 DEPS files not in the top-level of a module won't need this. Then you have | 21 DEPS files not in the top-level of a module won't need this. Then you |
22 any additional include rules. You can add (using "+") or subtract (using "-") | 22 have any additional include rules. You can add (using "+") or subtract |
23 from the previously specified rules (including module-level deps). | 23 (using "-") from the previously specified rules (including |
24 module-level deps). You can also specify a path that is allowed for | |
25 now but that we intend to remove, using "!"; this is treated the same | |
26 as "+" when check_deps is run by our bots, but a presubmit step will | |
27 show a warning if you add a new include of a file that is only allowed | |
28 by "!". | |
29 | |
30 Note that for .java files, there is currently no difference between | |
31 "+" and "!", even in the presubmit step. | |
24 | 32 |
25 include_rules = { | 33 include_rules = { |
26 # Code should be able to use base (it's specified in the module-level | 34 # Code should be able to use base (it's specified in the module-level |
27 # deps above), but nothing in "base/evil" because it's evil. | 35 # deps above), but nothing in "base/evil" because it's evil. |
28 "-base/evil", | 36 "-base/evil", |
29 | 37 |
30 # But this one subdirectory of evil is OK. | 38 # But this one subdirectory of evil is OK. |
31 "+base/evil/not", | 39 "+base/evil/not", |
32 | 40 |
33 # And it can include files from this other directory even though there is | 41 # And it can include files from this other directory even though there is |
34 # no deps rule for it. | 42 # no deps rule for it. |
35 "+tools/crime_fighter" | 43 "+tools/crime_fighter", |
44 | |
45 # This dependency is allowed for now but work is ongoing to remove it, | |
46 # so you shouldn't add further dependencies on it. | |
47 "!base/evil/ok_for_now.h", | |
36 } | 48 } |
37 | 49 |
38 DEPS files may be placed anywhere in the tree. Each one applies to all | 50 DEPS files may be placed anywhere in the tree. Each one applies to all |
39 subdirectories, where there may be more DEPS files that provide additions or | 51 subdirectories, where there may be more DEPS files that provide additions or |
40 subtractions for their own sub-trees. | 52 subtractions for their own sub-trees. |
41 | 53 |
42 There is an implicit rule for the current directory (where the DEPS file lives) | 54 There is an implicit rule for the current directory (where the DEPS file lives) |
43 and all of its subdirectories. This prevents you from having to explicitly | 55 and all of its subdirectories. This prevents you from having to explicitly |
44 allow the current directory everywhere. This implicit rule is applied first, | 56 allow the current directory everywhere. This implicit rule is applied first, |
45 so you can modify or remove it using the normal include rules. | 57 so you can modify or remove it using the normal include rules. |
46 | 58 |
47 The rules are processed in order. This means you can explicitly allow a higher | 59 The rules are processed in order. This means you can explicitly allow a higher |
48 directory and then take away permissions from sub-parts, or the reverse. | 60 directory and then take away permissions from sub-parts, or the reverse. |
49 | 61 |
50 Note that all directory separators must be slashes (Unix-style) and not | 62 Note that all directory separators must be slashes (Unix-style) and not |
51 backslashes. All directories should be relative to the source root and use | 63 backslashes. All directories should be relative to the source root and use |
52 only lowercase. | 64 only lowercase. |
53 """ | 65 """ |
54 | 66 |
55 import os | 67 import os |
56 import optparse | 68 import optparse |
57 import pipes | 69 import subprocess |
58 import sys | 70 import sys |
59 import copy | 71 import copy |
60 | 72 |
61 import cpp_checker | 73 import cpp_checker |
62 import java_checker | 74 import java_checker |
75 from rules import Rule, Rules | |
63 | 76 |
64 | 77 |
65 # Variable name used in the DEPS file to add or subtract include files from | 78 # Variable name used in the DEPS file to add or subtract include files from |
66 # the module-level deps. | 79 # the module-level deps. |
67 INCLUDE_RULES_VAR_NAME = "include_rules" | 80 INCLUDE_RULES_VAR_NAME = "include_rules" |
68 | 81 |
69 # Optionally present in the DEPS file to list subdirectories which should not | 82 # Optionally present in the DEPS file to list subdirectories which should not |
70 # be checked. This allows us to skip third party code, for example. | 83 # be checked. This allows us to skip third party code, for example. |
71 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" | 84 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" |
72 | 85 |
73 # Set to true for more output. This is set by the command line options. | 86 |
74 VERBOSE = False | 87 class DepsChecker(object): |
75 | 88 """Parses include_rules from DEPS files and can verify files in the |
76 # In lowercase, using forward slashes as directory separators, ending in a | 89 source tree against them. |
77 # forward slash. Set by the command line options. | |
78 BASE_DIRECTORY = "" | |
79 | |
80 # The directories which contain the sources managed by git. | |
81 GIT_SOURCE_DIRECTORY = set() | |
82 | |
83 | |
84 # Specifies a single rule for an include, which can be either allow or disallow. | |
85 class Rule(object): | |
86 def __init__(self, allow, directory, source): | |
87 self.allow = allow | |
88 self._dir = directory | |
89 self._source = source | |
90 | |
91 def __str__(self): | |
92 if (self.allow): | |
93 return '"+%s" from %s.' % (self._dir, self._source) | |
94 return '"-%s" from %s.' % (self._dir, self._source) | |
95 | |
96 def ParentOrMatch(self, other): | |
97 """Returns true if the input string is an exact match or is a parent | |
98 of the current rule. For example, the input "foo" would match "foo/bar".""" | |
99 return self._dir == other or self._dir.startswith(other + "/") | |
100 | |
101 def ChildOrMatch(self, other): | |
102 """Returns true if the input string would be covered by this rule. For | |
103 example, the input "foo/bar" would match the rule "foo".""" | |
104 return self._dir == other or other.startswith(self._dir + "/") | |
105 | |
106 | |
107 def ParseRuleString(rule_string, source): | |
108 """Returns a tuple of a boolean indicating whether the directory is an allow | |
109 rule, and a string holding the directory name. | |
110 """ | 90 """ |
111 if len(rule_string) < 1: | 91 |
112 raise Exception('The rule string "%s" is too short\nin %s' % | 92 def __init__(self, base_directory=None, verbose=False, being_tested=False): |
113 (rule_string, source)) | 93 """Creates a new DepsChecker. |
114 | 94 |
115 if rule_string[0] == "+": | 95 Args: |
116 return (True, rule_string[1:]) | 96 base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. |
117 if rule_string[0] == "-": | 97 verbose: Set to true for debug output. |
118 return (False, rule_string[1:]) | 98 being_tested: Set to true to ignore the DEPS file at tools/checkdeps/DEPS. |
119 raise Exception('The rule string "%s" does not begin with a "+" or a "-"' % | 99 """ |
120 rule_string) | 100 self.base_directory = base_directory |
121 | 101 if not base_directory: |
122 | 102 self.base_directory = os.path.abspath( |
123 class Rules: | 103 os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', '..')) |
124 def __init__(self): | 104 |
125 """Initializes the current rules with an empty rule list.""" | 105 self.verbose = verbose |
126 self._rules = [] | 106 |
127 | 107 self.git_source_directories = set() |
128 def __str__(self): | 108 self._AddGitSourceDirectories() |
129 ret = "Rules = [\n" | 109 |
130 ret += "\n".join([" %s" % x for x in self._rules]) | 110 self._under_test = being_tested |
131 ret += "]\n" | 111 |
132 return ret | 112 def _ApplyRules(self, existing_rules, includes, cur_dir): |
133 | 113 """Applies the given include rules, returning the new rules. |
134 def AddRule(self, rule_string, source): | 114 |
135 """Adds a rule for the given rule string. | 115 Args: |
136 | 116 existing_rules: A set of existing rules that will be combined. |
137 Args: | 117 include: The list of rules from the "include_rules" section of DEPS. |
138 rule_string: The include_rule string read from the DEPS file to apply. | 118 cur_dir: The current directory. We will create an implicit rule that |
139 source: A string representing the location of that string (filename, etc.) | 119 allows inclusion from this directory. |
140 so that we can give meaningful errors. | 120 |
141 """ | 121 Returns: A new set of rules combining the existing_rules with the other |
142 (add_rule, rule_dir) = ParseRuleString(rule_string, source) | 122 arguments. |
143 # Remove any existing rules or sub-rules that apply. For example, if we're | 123 """ |
144 # passed "foo", we should remove "foo", "foo/bar", but not "foobar". | 124 rules = copy.copy(existing_rules) |
145 self._rules = [x for x in self._rules if not x.ParentOrMatch(rule_dir)] | 125 |
146 self._rules.insert(0, Rule(add_rule, rule_dir, source)) | 126 # First apply the implicit "allow" rule for the current directory. |
147 | 127 if os.path.normpath(cur_dir).lower().startswith( |
148 def DirAllowed(self, allowed_dir): | 128 os.path.normpath(self.base_directory).lower()): |
149 """Returns a tuple (success, message), where success indicates if the given | 129 relative_dir = cur_dir[len(self.base_directory) + 1:] |
150 directory is allowed given the current set of rules, and the message tells | 130 source = relative_dir |
151 why if the comparison failed.""" | 131 if len(source) == 0: |
152 for rule in self._rules: | 132 source = "top level" # Make the help string a little more meaningful. |
153 if rule.ChildOrMatch(allowed_dir): | 133 rules.AddRule("+" + relative_dir, "Default rule for " + source) |
154 # This rule applies. | |
155 if rule.allow: | |
156 return (True, "") | |
157 return (False, rule.__str__()) | |
158 # No rules apply, fail. | |
159 return (False, "no rule applying") | |
160 | |
161 | |
162 def ApplyRules(existing_rules, includes, cur_dir): | |
163 """Applies the given include rules, returning the new rules. | |
164 | |
165 Args: | |
166 existing_rules: A set of existing rules that will be combined. | |
167 include: The list of rules from the "include_rules" section of DEPS. | |
168 cur_dir: The current directory. We will create an implicit rule that | |
169 allows inclusion from this directory. | |
170 | |
171 Returns: A new set of rules combining the existing_rules with the other | |
172 arguments. | |
173 """ | |
174 rules = copy.copy(existing_rules) | |
175 | |
176 # First apply the implicit "allow" rule for the current directory. | |
177 if cur_dir.lower().startswith(BASE_DIRECTORY): | |
178 relative_dir = cur_dir[len(BASE_DIRECTORY) + 1:] | |
179 # Normalize path separators to slashes. | |
180 relative_dir = relative_dir.replace("\\", "/") | |
181 source = relative_dir | |
182 if len(source) == 0: | |
183 source = "top level" # Make the help string a little more meaningful. | |
184 rules.AddRule("+" + relative_dir, "Default rule for " + source) | |
185 else: | |
186 raise Exception("Internal error: base directory is not at the beginning" + | |
187 " for\n %s and base dir\n %s" % | |
188 (cur_dir, BASE_DIRECTORY)) | |
189 | |
190 # Last, apply the additional explicit rules. | |
191 for (_, rule_str) in enumerate(includes): | |
192 if not len(relative_dir): | |
193 rule_description = "the top level include_rules" | |
194 else: | 134 else: |
195 rule_description = relative_dir + "'s include_rules" | 135 raise Exception("Internal error: base directory is not at the beginning" + |
196 rules.AddRule(rule_str, rule_description) | 136 " for\n %s and base dir\n %s" % |
197 | 137 (cur_dir, self.base_directory)) |
198 return rules | 138 |
199 | 139 # Last, apply the additional explicit rules. |
200 | 140 for (_, rule_str) in enumerate(includes): |
M-A Ruel
2012/07/26 18:30:07
I know you just moved the code but while at it, re
| |
201 def ApplyDirectoryRules(existing_rules, dir_name): | 141 if not len(relative_dir): |
M-A Ruel
2012/07/26 18:30:07
if not relative_dir:
| |
202 """Combines rules from the existing rules and the new directory. | 142 rule_description = "the top level include_rules" |
203 | 143 else: |
204 Any directory can contain a DEPS file. Toplevel DEPS files can contain | 144 rule_description = relative_dir + "'s include_rules" |
205 module dependencies which are used by gclient. We use these, along with | 145 rules.AddRule(rule_str, rule_description) |
206 additional include rules and implicit rules for the given directory, to | 146 |
207 come up with a combined set of rules to apply for the directory. | 147 return rules |
208 | 148 |
209 Args: | 149 def _ApplyDirectoryRules(self, existing_rules, dir_name): |
210 existing_rules: The rules for the parent directory. We'll add-on to these. | 150 """Combines rules from the existing rules and the new directory. |
211 dir_name: The directory name that the deps file may live in (if it exists). | 151 |
212 This will also be used to generate the implicit rules. | 152 Any directory can contain a DEPS file. Toplevel DEPS files can contain |
213 | 153 module dependencies which are used by gclient. We use these, along with |
214 Returns: A tuple containing: (1) the combined set of rules to apply to the | 154 additional include rules and implicit rules for the given directory, to |
215 sub-tree, and (2) a list of all subdirectories that should NOT be | 155 come up with a combined set of rules to apply for the directory. |
216 checked, as specified in the DEPS file (if any). | 156 |
217 """ | 157 Args: |
218 # Check for a .svn directory in this directory or check this directory is | 158 existing_rules: The rules for the parent directory. We'll add-on to these. |
219 # contained in git source direcotries. This will tell us if it's a source | 159 dir_name: The directory name that the deps file may live in (if |
220 # directory and should be checked. | 160 it exists). This will also be used to generate the |
221 if not (os.path.exists(os.path.join(dir_name, ".svn")) or | 161 implicit rules. |
222 (dir_name.lower() in GIT_SOURCE_DIRECTORY)): | 162 |
223 return (None, []) | 163 Returns: A tuple containing: (1) the combined set of rules to apply to the |
224 | 164 sub-tree, and (2) a list of all subdirectories that should NOT be |
225 # Check the DEPS file in this directory. | 165 checked, as specified in the DEPS file (if any). |
226 if VERBOSE: | 166 """ |
227 print "Applying rules from", dir_name | 167 # Check for a .svn directory in this directory or check this directory is |
228 def FromImpl(_unused, _unused2): | 168 # contained in git source direcotries. This will tell us if it's a source |
229 pass # NOP function so "From" doesn't fail. | 169 # directory and should be checked. |
230 | 170 if not (os.path.exists(os.path.join(dir_name, ".svn")) or |
231 def FileImpl(_unused): | 171 (dir_name.lower() in self.git_source_directories)): |
232 pass # NOP function so "File" doesn't fail. | 172 return (None, []) |
233 | 173 |
234 class _VarImpl: | 174 # Check the DEPS file in this directory. |
235 def __init__(self, local_scope): | 175 if self.verbose: |
236 self._local_scope = local_scope | 176 print "Applying rules from", dir_name |
237 | 177 def FromImpl(_unused, _unused2): |
238 def Lookup(self, var_name): | 178 pass # NOP function so "From" doesn't fail. |
239 """Implements the Var syntax.""" | 179 |
240 if var_name in self._local_scope.get("vars", {}): | 180 def FileImpl(_unused): |
241 return self._local_scope["vars"][var_name] | 181 pass # NOP function so "File" doesn't fail. |
242 raise Exception("Var is not defined: %s" % var_name) | 182 |
243 | 183 class _VarImpl: |
244 local_scope = {} | 184 def __init__(self, local_scope): |
245 global_scope = { | 185 self._local_scope = local_scope |
246 "File": FileImpl, | 186 |
247 "From": FromImpl, | 187 def Lookup(self, var_name): |
248 "Var": _VarImpl(local_scope).Lookup, | 188 """Implements the Var syntax.""" |
249 } | 189 if var_name in self._local_scope.get("vars", {}): |
250 deps_file = os.path.join(dir_name, "DEPS") | 190 return self._local_scope["vars"][var_name] |
251 | 191 raise Exception("Var is not defined: %s" % var_name) |
252 if os.path.isfile(deps_file): | 192 |
253 execfile(deps_file, global_scope, local_scope) | 193 local_scope = {} |
254 elif VERBOSE: | 194 global_scope = { |
255 print " No deps file found in", dir_name | 195 "File": FileImpl, |
256 | 196 "From": FromImpl, |
257 # Even if a DEPS file does not exist we still invoke ApplyRules | 197 "Var": _VarImpl(local_scope).Lookup, |
258 # to apply the implicit "allow" rule for the current directory | 198 } |
259 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) | 199 deps_file = os.path.join(dir_name, "DEPS") |
260 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) | 200 |
261 | 201 # The second conditional here is to disregard the |
262 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs) | 202 # tools/checkdeps/DEPS file while running tests. This DEPS file |
263 | 203 # has a skip_child_includes for 'testdata' which is necessary for |
264 | 204 # running production tests, since there are intentional DEPS |
265 def CheckDirectory(parent_rules, checkers, dir_name): | 205 # violations under the testdata directory. On the other hand when |
266 (rules, skip_subdirs) = ApplyDirectoryRules(parent_rules, dir_name) | 206 # running tests, we absolutely need to verify the contents of that |
267 if rules == None: | 207 # directory to trigger those intended violations and see that they |
268 return True | 208 # are handled correctly. |
269 | 209 if os.path.isfile(deps_file) and ( |
270 # Collect a list of all files and directories to check. | 210 not self._under_test or not os.path.split(dir_name)[1] == 'checkdeps'): |
271 files_to_check = [] | 211 execfile(deps_file, global_scope, local_scope) |
272 dirs_to_check = [] | 212 elif self.verbose: |
273 success = True | 213 print " No deps file found in", dir_name |
274 contents = os.listdir(dir_name) | 214 |
275 for cur in contents: | 215 # Even if a DEPS file does not exist we still invoke ApplyRules |
276 if cur in skip_subdirs: | 216 # to apply the implicit "allow" rule for the current directory |
277 continue # Don't check children that DEPS has asked us to skip. | 217 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) |
278 full_name = os.path.join(dir_name, cur) | 218 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) |
279 if os.path.isdir(full_name): | 219 |
280 dirs_to_check.append(full_name) | 220 return (self._ApplyRules(existing_rules, include_rules, dir_name), |
281 elif os.path.splitext(full_name)[1] in checkers: | 221 skip_subdirs) |
282 files_to_check.append(full_name) | 222 |
283 | 223 def CheckDirectory(self, start_dir): |
284 # First check all files in this directory. | 224 """Checks all relevant source files in the specified directory and |
285 for cur in files_to_check: | 225 its subdirectories for compliance with DEPS rules throughout the |
286 checker = checkers[os.path.splitext(cur)[1]] | 226 tree (starting at |self.base_directory|). |start_dir| must be a |
287 file_status = checker.CheckFile(rules, cur) | 227 subdirectory of |self.base_directory|. |
288 if file_status: | 228 |
289 print "ERROR in " + cur + "\n" + file_status | 229 Returns an empty array on success. On failure, the array contains |
290 success = False | 230 strings that can be printed as human-readable error messages. |
291 | 231 """ |
292 # Next recurse into the subdirectories. | 232 # TODO(joi): Make this work for start_dir != base_dir (I have a |
293 for cur in dirs_to_check: | 233 # subsequent change in flight to do this). |
294 if not CheckDirectory(rules, checkers, cur): | 234 base_rules = Rules() |
295 success = False | 235 java = java_checker.JavaChecker(self.base_directory, self.verbose) |
296 | 236 cpp = cpp_checker.CppChecker(self.verbose) |
297 return success | 237 checkers = dict( |
298 | 238 (extension, checker) |
299 | 239 for checker in [java, cpp] for extension in checker.EXTENSIONS) |
300 def GetGitSourceDirectory(root): | 240 return self._CheckDirectoryImpl(base_rules, checkers, start_dir) |
301 """Returns a set of the directories to be checked. | 241 |
302 | 242 def _CheckDirectoryImpl(self, parent_rules, checkers, dir_name): |
303 Args: | 243 (rules, skip_subdirs) = self._ApplyDirectoryRules(parent_rules, dir_name) |
304 root: The repository root where .git directory exists. | 244 if rules == None: |
305 | 245 return [] |
306 Returns: | 246 |
307 A set of directories which contain sources managed by git. | 247 # Collect a list of all files and directories to check. |
308 """ | 248 files_to_check = [] |
309 git_source_directory = set() | 249 dirs_to_check = [] |
310 popen_out = os.popen("cd %s && git ls-files --full-name ." % | 250 results = [] |
311 pipes.quote(root)) | 251 contents = os.listdir(dir_name) |
312 for line in popen_out.readlines(): | 252 for cur in contents: |
313 dir_name = os.path.join(root, os.path.dirname(line)) | 253 if cur in skip_subdirs: |
314 # Add the directory as well as all the parent directories. | 254 continue # Don't check children that DEPS has asked us to skip. |
315 while dir_name != root: | 255 full_name = os.path.join(dir_name, cur) |
316 git_source_directory.add(dir_name) | 256 if os.path.isdir(full_name): |
317 dir_name = os.path.dirname(dir_name) | 257 dirs_to_check.append(full_name) |
318 git_source_directory.add(root) | 258 elif os.path.splitext(full_name)[1] in checkers: |
319 return git_source_directory | 259 files_to_check.append(full_name) |
260 | |
261 | |
M-A Ruel
2012/07/26 18:30:07
one line is sufficient
| |
262 # First check all files in this directory. | |
263 for cur in files_to_check: | |
264 checker = checkers[os.path.splitext(cur)[1]] | |
265 file_status = checker.CheckFile(rules, cur) | |
266 if file_status: | |
267 results.append("ERROR in " + cur + "\n" + file_status) | |
268 | |
269 # Next recurse into the subdirectories. | |
270 for cur in dirs_to_check: | |
271 results.extend(self._CheckDirectoryImpl(rules, checkers, cur)) | |
272 return results | |
273 | |
274 def CheckAddedCppIncludes(self, added_includes): | |
275 """This is used from PRESUBMIT.py to check new #include statements added in | |
276 the change being presubmit checked. | |
277 | |
278 Args: | |
279 added_includes: ((file_path, (include_line, include_line, ...), ...) | |
280 | |
281 Return: | |
282 A list of tuples, (bad_file_path, rule_type, rule_description) | |
283 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and | |
284 rule_description is human-readable. Empty if no problems. | |
285 """ | |
286 # Map of directory paths to rules to use for those directories, or | |
287 # None for directories that should be skipped. | |
288 directory_rules = {} | |
289 | |
290 def ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path): | |
291 rules_tuple = self._ApplyDirectoryRules(parent_rules, dir_path) | |
292 directory_rules[dir_path] = rules_tuple[0] | |
293 for subdir in rules_tuple[1]: | |
294 # We skip this one case for running tests. | |
295 directory_rules[os.path.join(dir_path, subdir)] = None | |
296 | |
297 ApplyDirectoryRulesAndSkipSubdirs(Rules(), self.base_directory) | |
298 | |
299 def GetDirectoryRules(dir_path): | |
300 """Returns a Rules object to use for the given directory, or None | |
301 if the given directory should be skipped. | |
302 """ | |
303 if not dir_path.startswith(self.base_directory): | |
304 dir_path = os.path.join(self.base_directory, dir_path) | |
305 | |
306 parent_dir = os.path.dirname(dir_path) | |
307 parent_rules = None | |
308 if not dir_path in directory_rules: | |
309 parent_rules = GetDirectoryRules(parent_dir) | |
310 | |
311 # We need to check for an entry for our dir_path again, in case we | |
312 # are at a path e.g. A/B/C where A/B/DEPS specifies the C | |
313 # subdirectory to be skipped; in this case, the invocation to | |
314 # GetDirectoryRules(parent_dir) has already filled in an entry for | |
315 # A/B/C. | |
316 if not dir_path in directory_rules: | |
317 if not parent_rules: | |
318 # If the parent directory should be skipped, then the current | |
319 # directory should also be skipped. | |
320 directory_rules[dir_path] = None | |
321 else: | |
322 ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path) | |
323 return directory_rules[dir_path] | |
324 | |
325 cpp = cpp_checker.CppChecker(self.verbose) | |
326 | |
327 problems = [] | |
328 for file_path, include_lines in added_includes: | |
329 # TODO(joi): Make this cover Java as well. | |
330 if not os.path.splitext(file_path)[1] in cpp.EXTENSIONS: | |
331 pass | |
332 rules_for_file = GetDirectoryRules(os.path.dirname(file_path)) | |
333 if rules_for_file: | |
334 for line in include_lines: | |
335 is_include, line_status, rule_type = cpp.CheckLine( | |
336 rules_for_file, line, True) | |
337 if rule_type != Rule.ALLOW: | |
338 problems.append((file_path, rule_type, line_status)) | |
339 return problems | |
340 | |
341 def _AddGitSourceDirectories(self): | |
342 """Adds any directories containing sources managed by git to | |
343 self.git_source_directories. | |
344 """ | |
345 if not os.path.exists(os.path.join(self.base_directory, ".git")): | |
346 return | |
347 | |
348 popen_out = os.popen("cd %s && git ls-files --full-name ." % | |
349 subprocess.list2cmdline([self.base_directory])) | |
350 for line in popen_out.readlines(): | |
351 dir_name = os.path.join(self.base_directory, os.path.dirname(line)) | |
352 # Add the directory as well as all the parent directories. | |
353 while dir_name != self.base_directory: | |
354 self.git_source_directories.add(dir_name.lower()) | |
355 dir_name = os.path.dirname(dir_name) | |
356 self.git_source_directories.add(self.base_directory.lower()) | |
320 | 357 |
321 | 358 |
322 def PrintUsage(): | 359 def PrintUsage(): |
323 print """Usage: python checkdeps.py [--root <root>] [tocheck] | 360 print """Usage: python checkdeps.py [--root <root>] [tocheck] |
324 --root Specifies the repository root. This defaults to "../../.." relative | 361 --root Specifies the repository root. This defaults to "../../.." relative |
325 to the script file. This will be correct given the normal location | 362 to the script file. This will be correct given the normal location |
326 of the script in "<root>/tools/checkdeps". | 363 of the script in "<root>/tools/checkdeps". |
327 | 364 |
328 tocheck Specifies the directory, relative to root, to check. This defaults | 365 tocheck Specifies the directory, relative to root, to check. This defaults |
329 to "." so it checks everything. Only one level deep is currently | 366 to "." so it checks everything. Only one level deep is currently |
330 supported, so you can say "chrome" but not "chrome/browser". | 367 supported, so you can say "chrome" but not "chrome/browser". |
331 | 368 |
332 Examples: | 369 Examples: |
333 python checkdeps.py | 370 python checkdeps.py |
334 python checkdeps.py --root c:\\source chrome""" | 371 python checkdeps.py --root c:\\source chrome""" |
335 | 372 |
336 | 373 |
337 def checkdeps(options, args): | |
338 global VERBOSE | |
339 if options.verbose: | |
340 VERBOSE = True | |
341 | |
342 # Optional base directory of the repository. | |
343 global BASE_DIRECTORY | |
344 if not options.base_directory: | |
345 BASE_DIRECTORY = os.path.abspath( | |
346 os.path.join(os.path.abspath(os.path.dirname(__file__)), "../..")) | |
347 else: | |
348 BASE_DIRECTORY = os.path.abspath(options.base_directory) | |
349 | |
350 # Figure out which directory we have to check. | |
351 if len(args) == 0: | |
352 # No directory to check specified, use the repository root. | |
353 start_dir = BASE_DIRECTORY | |
354 elif len(args) == 1: | |
355 # Directory specified. Start here. It's supposed to be relative to the | |
356 # base directory. | |
357 start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0])) | |
358 else: | |
359 # More than one argument, we don't handle this. | |
360 PrintUsage() | |
361 return 1 | |
362 | |
363 print "Using base directory:", BASE_DIRECTORY | |
364 print "Checking:", start_dir | |
365 | |
366 base_rules = Rules() | |
367 | |
368 # The base directory should be lower case from here on since it will be used | |
369 # for substring matching on the includes, and we compile on case-insensitive | |
370 # systems. Plus, we always use slashes here since the include parsing code | |
371 # will also normalize to slashes. | |
372 BASE_DIRECTORY = BASE_DIRECTORY.lower() | |
373 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") | |
374 start_dir = start_dir.replace("\\", "/") | |
375 | |
376 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): | |
377 global GIT_SOURCE_DIRECTORY | |
378 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) | |
379 | |
380 java = java_checker.JavaChecker(BASE_DIRECTORY, VERBOSE) | |
381 cpp = cpp_checker.CppChecker(VERBOSE) | |
382 checkers = dict( | |
383 (extension, checker) | |
384 for checker in [java, cpp] for extension in checker.EXTENSIONS) | |
385 success = CheckDirectory(base_rules, checkers, start_dir) | |
386 if not success: | |
387 print "\nFAILED\n" | |
388 return 1 | |
389 print "\nSUCCESS\n" | |
390 return 0 | |
391 | |
392 | |
393 def main(): | 374 def main(): |
394 option_parser = optparse.OptionParser() | 375 option_parser = optparse.OptionParser() |
395 option_parser.add_option("", "--root", default="", dest="base_directory", | 376 option_parser.add_option("", "--root", default="", dest="base_directory", |
396 help='Specifies the repository root. This defaults ' | 377 help='Specifies the repository root. This defaults ' |
397 'to "../../.." relative to the script file, which ' | 378 'to "../../.." relative to the script file, which ' |
398 'will normally be the repository root.') | 379 'will normally be the repository root.') |
399 option_parser.add_option("-v", "--verbose", action="store_true", | 380 option_parser.add_option("-v", "--verbose", action="store_true", |
400 default=False, help="Print debug logging") | 381 default=False, help="Print debug logging") |
401 options, args = option_parser.parse_args() | 382 options, args = option_parser.parse_args() |
402 return checkdeps(options, args) | 383 |
384 deps_checker = DepsChecker(options.base_directory, options.verbose) | |
385 | |
386 # Figure out which directory we have to check. | |
387 start_dir = deps_checker.base_directory | |
388 if len(args) == 1: | |
389 # Directory specified. Start here. It's supposed to be relative to the | |
390 # base directory. | |
391 start_dir = os.path.abspath( | |
392 os.path.join(deps_checker.base_directory, args[0])) | |
393 elif len(args) >= 2: | |
394 # More than one argument, we don't handle this. | |
395 PrintUsage() | |
396 return 1 | |
397 | |
398 print "Using base directory:", deps_checker.base_directory | |
399 print "Checking:", start_dir | |
400 | |
401 results = deps_checker.CheckDirectory(start_dir) | |
402 if results: | |
403 for result in results: | |
404 print result | |
405 print "\nFAILED\n" | |
406 return 1 | |
407 print "\nSUCCESS\n" | |
408 return 0 | |
403 | 409 |
404 | 410 |
405 if '__main__' == __name__: | 411 if '__main__' == __name__: |
406 sys.exit(main()) | 412 sys.exit(main()) |
OLD | NEW |