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 | |
jam
2012/07/20 20:08:49
it seems that this should be an error for presubmi
Jói
2012/07/20 21:57:39
In some cases, adding no new includes may be diffi
jam
2012/07/20 23:23:34
I don't feel strongly about this either, it's a pe
| |
28 by "!". | |
24 | 29 |
25 include_rules = { | 30 include_rules = { |
26 # Code should be able to use base (it's specified in the module-level | 31 # 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. | 32 # deps above), but nothing in "base/evil" because it's evil. |
28 "-base/evil", | 33 "-base/evil", |
29 | 34 |
30 # But this one subdirectory of evil is OK. | 35 # But this one subdirectory of evil is OK. |
31 "+base/evil/not", | 36 "+base/evil/not", |
32 | 37 |
33 # And it can include files from this other directory even though there is | 38 # And it can include files from this other directory even though there is |
34 # no deps rule for it. | 39 # no deps rule for it. |
35 "+tools/crime_fighter" | 40 "+tools/crime_fighter", |
41 | |
42 # This dependency is allowed for now but work is ongoing to remove it, | |
43 # so you shouldn't add further dependencies on it. | |
44 "!base/evil/ok_for_now.h", | |
36 } | 45 } |
37 | 46 |
38 DEPS files may be placed anywhere in the tree. Each one applies to all | 47 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 | 48 subdirectories, where there may be more DEPS files that provide additions or |
40 subtractions for their own sub-trees. | 49 subtractions for their own sub-trees. |
41 | 50 |
42 There is an implicit rule for the current directory (where the DEPS file lives) | 51 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 | 52 and all of its subdirectories. This prevents you from having to explicitly |
44 allow the current directory everywhere. This implicit rule is applied first, | 53 allow the current directory everywhere. This implicit rule is applied first, |
45 so you can modify or remove it using the normal include rules. | 54 so you can modify or remove it using the normal include rules. |
46 | 55 |
47 The rules are processed in order. This means you can explicitly allow a higher | 56 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. | 57 directory and then take away permissions from sub-parts, or the reverse. |
49 | 58 |
50 Note that all directory separators must be slashes (Unix-style) and not | 59 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 | 60 backslashes. All directories should be relative to the source root and use |
52 only lowercase. | 61 only lowercase. |
53 """ | 62 """ |
54 | 63 |
55 import os | 64 import os |
56 import optparse | 65 import optparse |
57 import pipes | |
58 import re | 66 import re |
67 import subprocess | |
59 import sys | 68 import sys |
60 import copy | 69 import copy |
61 | 70 |
62 # Variable name used in the DEPS file to add or subtract include files from | 71 # Variable name used in the DEPS file to add or subtract include files from |
63 # the module-level deps. | 72 # the module-level deps. |
64 INCLUDE_RULES_VAR_NAME = "include_rules" | 73 INCLUDE_RULES_VAR_NAME = "include_rules" |
65 | 74 |
66 # Optionally present in the DEPS file to list subdirectories which should not | 75 # Optionally present in the DEPS file to list subdirectories which should not |
67 # be checked. This allows us to skip third party code, for example. | 76 # be checked. This allows us to skip third party code, for example. |
68 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" | 77 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" |
69 | 78 |
70 # The maximum number of non-include lines we can see before giving up. | 79 # The maximum number of non-include lines we can see before giving up. |
71 MAX_UNINTERESTING_LINES = 50 | 80 MAX_UNINTERESTING_LINES = 50 |
72 | 81 |
73 # The maximum line length, this is to be efficient in the case of very long | 82 # The maximum line length, this is to be efficient in the case of very long |
74 # lines (which can't be #includes). | 83 # lines (which can't be #includes). |
75 MAX_LINE_LENGTH = 128 | 84 MAX_LINE_LENGTH = 128 |
76 | 85 |
77 # Set to true for more output. This is set by the command line options. | 86 # Set to true for more output. This is set by the command line options. |
78 VERBOSE = False | 87 VERBOSE = False |
79 | 88 |
80 # This regular expression will be used to extract filenames from include | 89 # This regular expression will be used to extract filenames from include |
81 # statements. | 90 # statements. |
82 EXTRACT_INCLUDE_PATH = re.compile('[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"') | 91 EXTRACT_INCLUDE_PATH = re.compile('[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"') |
83 | 92 |
84 # In lowercase, using forward slashes as directory separators, ending in a | 93 # OS-compatible path for base directory (e.g. C:\chrome\src). Set |
85 # forward slash. Set by the command line options. | 94 # implicitly or by command-line option. |
86 BASE_DIRECTORY = "" | 95 BASE_DIRECTORY = "" |
87 | 96 |
88 # The directories which contain the sources managed by git. | 97 # The directories which contain the sources managed by git. |
89 GIT_SOURCE_DIRECTORY = set() | 98 GIT_SOURCE_DIRECTORY = set() |
90 | 99 |
91 | 100 |
92 # Specifies a single rule for an include, which can be either allow or disallow. | 101 # Specifies a single rule for an include, which can be either allow or disallow. |
93 class Rule(object): | 102 class Rule(object): |
103 | |
104 # These are the prefixes used to indicate each type of rule. These | |
105 # are also used as values for self._allow to indicate which type of | |
106 # rule this is. | |
107 ALLOW = "+" | |
108 DISALLOW = "-" | |
109 TEMP_ALLOW = "!" | |
110 | |
94 def __init__(self, allow, dir, source): | 111 def __init__(self, allow, dir, source): |
95 self._allow = allow | 112 self._allow = allow |
96 self._dir = dir | 113 self._dir = dir |
97 self._source = source | 114 self._source = source |
98 | 115 |
99 def __str__(self): | 116 def __str__(self): |
100 if (self._allow): | 117 return '"%s%s" from %s.' % (self._allow, self._dir, self._source) |
101 return '"+%s" from %s.' % (self._dir, self._source) | |
102 return '"-%s" from %s.' % (self._dir, self._source) | |
103 | 118 |
104 def ParentOrMatch(self, other): | 119 def ParentOrMatch(self, other): |
105 """Returns true if the input string is an exact match or is a parent | 120 """Returns true if the input string is an exact match or is a parent |
106 of the current rule. For example, the input "foo" would match "foo/bar".""" | 121 of the current rule. For example, the input "foo" would match "foo/bar".""" |
107 return self._dir == other or self._dir.startswith(other + "/") | 122 return self._dir == other or self._dir.startswith(other + "/") |
108 | 123 |
109 def ChildOrMatch(self, other): | 124 def ChildOrMatch(self, other): |
110 """Returns true if the input string would be covered by this rule. For | 125 """Returns true if the input string would be covered by this rule. For |
111 example, the input "foo/bar" would match the rule "foo".""" | 126 example, the input "foo/bar" would match the rule "foo".""" |
112 return self._dir == other or other.startswith(self._dir + "/") | 127 return self._dir == other or other.startswith(self._dir + "/") |
113 | 128 |
114 | 129 |
115 def ParseRuleString(rule_string, source): | 130 def ParseRuleString(rule_string, source): |
116 """Returns a tuple of a boolean indicating whether the directory is an allow | 131 """Returns a tuple of a boolean indicating whether the directory is an allow |
117 rule, and a string holding the directory name. | 132 rule, and a string holding the directory name. |
118 """ | 133 """ |
119 if len(rule_string) < 1: | 134 if len(rule_string) < 1: |
120 raise Exception('The rule string "%s" is too short\nin %s' % | 135 raise Exception('The rule string "%s" is too short\nin %s' % |
121 (rule_string, source)) | 136 (rule_string, source)) |
122 | 137 |
123 if rule_string[0] == "+": | 138 if not rule_string[0] in [Rule.ALLOW, Rule.DISALLOW, Rule.TEMP_ALLOW]: |
124 return (True, rule_string[1:]) | 139 raise Exception( |
125 if rule_string[0] == "-": | 140 'The rule string "%s" does not begin with a "+", "-" or "!".' % |
126 return (False, rule_string[1:]) | 141 rule_string) |
127 raise Exception('The rule string "%s" does not begin with a "+" or a "-"' % | 142 |
128 rule_string) | 143 return (rule_string[0], rule_string[1:]) |
129 | 144 |
130 | 145 |
131 class Rules: | 146 class Rules: |
132 def __init__(self): | 147 def __init__(self): |
133 """Initializes the current rules with an empty rule list.""" | 148 """Initializes the current rules with an empty rule list.""" |
134 self._rules = [] | 149 self._rules = [] |
135 | 150 |
136 def __str__(self): | 151 def __str__(self): |
137 ret = "Rules = [\n" | 152 ret = "Rules = [\n" |
138 ret += "\n".join([" %s" % x for x in self._rules]) | 153 ret += "\n".join([" %s" % x for x in self._rules]) |
(...skipping 14 matching lines...) Expand all Loading... | |
153 self._rules = [x for x in self._rules if not x.ParentOrMatch(rule_dir)] | 168 self._rules = [x for x in self._rules if not x.ParentOrMatch(rule_dir)] |
154 self._rules.insert(0, Rule(add_rule, rule_dir, source)) | 169 self._rules.insert(0, Rule(add_rule, rule_dir, source)) |
155 | 170 |
156 def DirAllowed(self, allowed_dir): | 171 def DirAllowed(self, allowed_dir): |
157 """Returns a tuple (success, message), where success indicates if the given | 172 """Returns a tuple (success, message), where success indicates if the given |
158 directory is allowed given the current set of rules, and the message tells | 173 directory is allowed given the current set of rules, and the message tells |
159 why if the comparison failed.""" | 174 why if the comparison failed.""" |
160 for rule in self._rules: | 175 for rule in self._rules: |
161 if rule.ChildOrMatch(allowed_dir): | 176 if rule.ChildOrMatch(allowed_dir): |
162 # This rule applies. | 177 # This rule applies. |
163 if rule._allow: | 178 why_failed = "" |
164 return (True, "") | 179 if rule._allow != Rule.ALLOW: |
165 return (False, rule.__str__()) | 180 why_failed = rule.__str__() |
181 return (rule._allow, why_failed) | |
166 # No rules apply, fail. | 182 # No rules apply, fail. |
167 return (False, "no rule applying") | 183 return (Rule.DISALLOW, "no rule applying") |
168 | 184 |
169 | 185 |
170 def ApplyRules(existing_rules, includes, cur_dir): | 186 def ApplyRules(existing_rules, includes, cur_dir): |
171 """Applies the given include rules, returning the new rules. | 187 """Applies the given include rules, returning the new rules. |
172 | 188 |
173 Args: | 189 Args: |
174 existing_rules: A set of existing rules that will be combined. | 190 existing_rules: A set of existing rules that will be combined. |
175 include: The list of rules from the "include_rules" section of DEPS. | 191 include: The list of rules from the "include_rules" section of DEPS. |
176 cur_dir: The current directory. We will create an implicit rule that | 192 cur_dir: The current directory. We will create an implicit rule that |
177 allows inclusion from this directory. | 193 allows inclusion from this directory. |
178 | 194 |
179 Returns: A new set of rules combining the existing_rules with the other | 195 Returns: A new set of rules combining the existing_rules with the other |
180 arguments. | 196 arguments. |
181 """ | 197 """ |
182 rules = copy.copy(existing_rules) | 198 rules = copy.copy(existing_rules) |
183 | 199 |
184 # First apply the implicit "allow" rule for the current directory. | 200 # First apply the implicit "allow" rule for the current directory. |
185 if cur_dir.lower().startswith(BASE_DIRECTORY): | 201 if os.path.normcase(os.path.normpath(cur_dir)).startswith( |
202 os.path.normcase(os.path.normpath(BASE_DIRECTORY))): | |
186 relative_dir = cur_dir[len(BASE_DIRECTORY) + 1:] | 203 relative_dir = cur_dir[len(BASE_DIRECTORY) + 1:] |
187 # Normalize path separators to slashes. | |
188 relative_dir = relative_dir.replace("\\", "/") | |
189 source = relative_dir | 204 source = relative_dir |
190 if len(source) == 0: | 205 if len(source) == 0: |
191 source = "top level" # Make the help string a little more meaningful. | 206 source = "top level" # Make the help string a little more meaningful. |
192 rules.AddRule("+" + relative_dir, "Default rule for " + source) | 207 rules.AddRule("+" + relative_dir, "Default rule for " + source) |
193 else: | 208 else: |
194 raise Exception("Internal error: base directory is not at the beginning" + | 209 raise Exception("Internal error: base directory is not at the beginning" + |
195 " for\n %s and base dir\n %s" % | 210 " for\n %s and base dir\n %s" % |
196 (cur_dir, BASE_DIRECTORY)) | 211 (cur_dir, BASE_DIRECTORY)) |
197 | 212 |
198 # Last, apply the additional explicit rules. | 213 # Last, apply the additional explicit rules. |
(...skipping 21 matching lines...) Expand all Loading... | |
220 This will also be used to generate the implicit rules. | 235 This will also be used to generate the implicit rules. |
221 | 236 |
222 Returns: A tuple containing: (1) the combined set of rules to apply to the | 237 Returns: A tuple containing: (1) the combined set of rules to apply to the |
223 sub-tree, and (2) a list of all subdirectories that should NOT be | 238 sub-tree, and (2) a list of all subdirectories that should NOT be |
224 checked, as specified in the DEPS file (if any). | 239 checked, as specified in the DEPS file (if any). |
225 """ | 240 """ |
226 # Check for a .svn directory in this directory or check this directory is | 241 # Check for a .svn directory in this directory or check this directory is |
227 # contained in git source direcotries. This will tell us if it's a source | 242 # contained in git source direcotries. This will tell us if it's a source |
228 # directory and should be checked. | 243 # directory and should be checked. |
229 if not (os.path.exists(os.path.join(dir_name, ".svn")) or | 244 if not (os.path.exists(os.path.join(dir_name, ".svn")) or |
230 (dir_name.lower() in GIT_SOURCE_DIRECTORY)): | 245 (dir_name in GIT_SOURCE_DIRECTORY)): |
M-A Ruel
2012/07/20 19:57:01
are you sure?
Jói
2012/07/20 20:01:41
Yes, or Linux (and Mac, I guess) is broken for che
| |
231 return (None, []) | 246 return (None, []) |
232 | 247 |
233 # Check the DEPS file in this directory. | 248 # Check the DEPS file in this directory. |
234 if VERBOSE: | 249 if VERBOSE: |
235 print "Applying rules from", dir_name | 250 print "Applying rules from", dir_name |
236 def FromImpl(unused, unused2): | 251 def FromImpl(unused, unused2): |
237 pass # NOP function so "From" doesn't fail. | 252 pass # NOP function so "From" doesn't fail. |
238 | 253 |
239 def FileImpl(unused): | 254 def FileImpl(unused): |
240 pass # NOP function so "File" doesn't fail. | 255 pass # NOP function so "File" doesn't fail. |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
275 checked_extensions = [ | 290 checked_extensions = [ |
276 '.h', | 291 '.h', |
277 '.cc', | 292 '.cc', |
278 '.m', | 293 '.m', |
279 '.mm', | 294 '.mm', |
280 ] | 295 ] |
281 basename, extension = os.path.splitext(file_name) | 296 basename, extension = os.path.splitext(file_name) |
282 return extension in checked_extensions | 297 return extension in checked_extensions |
283 | 298 |
284 | 299 |
285 def CheckLine(rules, line): | 300 def CheckLine(rules, line, fail_on_temp_allow=False): |
286 """Checks the given file with the given rule set. | 301 """Checks the given line with the given rule set. |
287 Returns a tuple (is_include, illegal_description). | 302 Returns a triplet (is_include, illegal_description, rule_type). |
288 If the line is an #include directive the first value will be True. | 303 If the line is an #include directive the first value will be True. |
289 If it is also an illegal include, the second value will be a string describing | 304 If it is also an illegal include, the second value will be a string describing |
290 the error. Otherwise, it will be None.""" | 305 the error. Otherwise, it will be None. |
306 """ | |
291 found_item = EXTRACT_INCLUDE_PATH.match(line) | 307 found_item = EXTRACT_INCLUDE_PATH.match(line) |
292 if not found_item: | 308 if not found_item: |
293 return False, None # Not a match | 309 return False, None, Rule.ALLOW # Not a match |
294 | 310 |
295 include_path = found_item.group(1) | 311 include_path = found_item.group(1) |
296 | 312 |
297 # Fix up backslashes in case somebody accidentally used them. | 313 # Fix up backslashes in case somebody accidentally used them. |
298 include_path.replace("\\", "/") | 314 include_path.replace("\\", "/") |
299 | 315 |
300 if include_path.find("/") < 0: | 316 if include_path.find("/") < 0: |
301 # Don't fail when no directory is specified. We may want to be more | 317 # Don't fail when no directory is specified. We may want to be more |
302 # strict about this in the future. | 318 # strict about this in the future. |
303 if VERBOSE: | 319 if VERBOSE: |
304 print " WARNING: directory specified with no path: " + include_path | 320 print " WARNING: directory specified with no path: " + include_path |
305 return True, None | 321 return True, None, Rule.ALLOW |
306 | 322 |
307 (allowed, why_failed) = rules.DirAllowed(include_path) | 323 (allowed, why_failed) = rules.DirAllowed(include_path) |
308 if not allowed: | 324 if (allowed == Rule.DISALLOW or |
325 (fail_on_temp_allow and allowed == Rule.TEMP_ALLOW)): | |
309 if VERBOSE: | 326 if VERBOSE: |
310 retval = "\nFor " + rules.__str__() | 327 retval = "\nFor " + rules.__str__() |
311 else: | 328 else: |
312 retval = "" | 329 retval = "" |
313 return True, retval + ('Illegal include: "%s"\n Because of %s' % | 330 return True, retval + ('Illegal include: "%s"\n Because of %s' % |
314 (include_path, why_failed)) | 331 (include_path, why_failed)), allowed |
315 | 332 |
316 return True, None | 333 return True, None, Rule.ALLOW |
317 | 334 |
318 | 335 |
319 def CheckFile(rules, file_name): | 336 def CheckFile(rules, file_name): |
320 """Checks the given file with the given rule set. | 337 """Checks the given file with the given rule set. |
321 | 338 |
322 Args: | 339 Args: |
323 rules: The set of rules that apply to files in this directory. | 340 rules: The set of rules that apply to files in this directory. |
324 file_name: The source file to check. | 341 file_name: The source file to check. |
325 | 342 |
326 Returns: Either a string describing the error if there was one, or None if | 343 Returns: Either a string describing the error if there was one, or None if |
(...skipping 20 matching lines...) Expand all Loading... | |
347 if cur_line == '#if 0': | 364 if cur_line == '#if 0': |
348 in_if0 += 1 | 365 in_if0 += 1 |
349 continue | 366 continue |
350 if in_if0 > 0: | 367 if in_if0 > 0: |
351 if cur_line.startswith('#if'): | 368 if cur_line.startswith('#if'): |
352 in_if0 += 1 | 369 in_if0 += 1 |
353 elif cur_line == '#endif': | 370 elif cur_line == '#endif': |
354 in_if0 -= 1 | 371 in_if0 -= 1 |
355 continue | 372 continue |
356 | 373 |
357 is_include, line_status = CheckLine(rules, cur_line) | 374 is_include, line_status, rule_type = CheckLine(rules, cur_line) |
358 if is_include: | 375 if is_include: |
359 last_include = line_num | 376 last_include = line_num |
360 if line_status is not None: | 377 if line_status is not None: |
361 if len(line_status) > 0: # Add newline to separate messages. | 378 if len(line_status) > 0: # Add newline to separate messages. |
362 line_status += "\n" | 379 line_status += "\n" |
363 ret_val += line_status | 380 ret_val += line_status |
364 cur_file.close() | 381 cur_file.close() |
365 | 382 |
366 except IOError: | 383 except IOError: |
367 if VERBOSE: | 384 if VERBOSE: |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
401 success = False | 418 success = False |
402 | 419 |
403 # Next recurse into the subdirectories. | 420 # Next recurse into the subdirectories. |
404 for cur in dirs_to_check: | 421 for cur in dirs_to_check: |
405 if not CheckDirectory(rules, cur): | 422 if not CheckDirectory(rules, cur): |
406 success = False | 423 success = False |
407 | 424 |
408 return success | 425 return success |
409 | 426 |
410 | 427 |
428 def CheckAddedIncludes(added_includes): | |
429 """This is used from PRESUBMIT.py to check new #include statements added in | |
430 the change being presubmit checked. | |
431 | |
432 Args: | |
433 added_includes: ((file_path, (include_line, include_line, ...), ...) | |
434 | |
435 Return: | |
436 A list of tuples, (bad_file_path, rule_type, rule_description) | |
437 where rule_type is one of Rule.DISALLOW or Rule.TEMP_ALLOW and | |
438 rule_description is human-readable. Empty if no problems. | |
439 """ | |
440 CommonSetup() | |
441 | |
442 # Map of directory paths to rules to use for those directories, or | |
443 # None for directories that should be skipped. | |
444 directory_rules = {} | |
445 | |
446 def ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path): | |
447 rules_tuple = ApplyDirectoryRules(parent_rules, dir_path) | |
448 directory_rules[dir_path] = rules_tuple[0] | |
449 for subdir in rules_tuple[1]: | |
450 directory_rules[os.path.join(dir_path, subdir)] = None | |
451 | |
452 ApplyDirectoryRulesAndSkipSubdirs(Rules(), BASE_DIRECTORY) | |
453 | |
454 def GetDirectoryRules(dir_path): | |
455 """Returns a Rules object to use for the given directory, or None | |
456 if the given directory should be skipped. | |
457 """ | |
458 if not dir_path.startswith(BASE_DIRECTORY): | |
459 dir_path = os.path.join(BASE_DIRECTORY, dir_path) | |
460 | |
461 parent_dir = os.path.dirname(dir_path) | |
462 parent_rules = None | |
463 if not dir_path in directory_rules: | |
464 parent_rules = GetDirectoryRules(parent_dir) | |
465 | |
466 # We need to check for an entry for our dir_path again, in case we | |
467 # are at a path e.g. A/B/C where A/B/DEPS specifies the C | |
468 # subdirectory to be skipped; in this case, the invocation to | |
469 # GetDirectoryRules(parent_dir) has already filled in an entry for | |
470 # A/B/C. | |
471 if not dir_path in directory_rules: | |
472 if not parent_rules: | |
473 # If the parent directory should be skipped, then the current | |
474 # directory should also be skipped. | |
475 directory_rules[dir_path] = None | |
476 else: | |
477 ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path) | |
478 return directory_rules[dir_path] | |
479 | |
480 problems = [] | |
481 for file_path, include_lines in added_includes: | |
482 if not ShouldCheckFile(file_path): | |
483 pass | |
484 rules_for_file = GetDirectoryRules(os.path.dirname(file_path)) | |
485 if rules_for_file: | |
486 for line in include_lines: | |
487 is_include, line_status, rule_type = CheckLine( | |
488 rules_for_file, line, True) | |
489 if rule_type != Rule.ALLOW: | |
490 problems.append((file_path, rule_type, line_status)) | |
491 return problems | |
492 | |
493 | |
494 def TestCheckAddedIncludes(): | |
495 """Run this by giving the argument 'manualtest' to checkdeps.""" | |
496 # Note that these tests depend on the rules currently in DEPS files | |
497 # checked into the tree, which may change. Therefore the test is | |
498 # only run manually and may need to be updated from time to time. | |
499 problems = CheckAddedIncludes( | |
500 # The first of these should fail as a DISALLOW, the second should | |
501 # fail as a TEMP_ALLOW, because of rules in chrome/browser/DEPS. | |
502 [['chrome/browser/bingo.cc', | |
503 ['#include "chrome/browser/ui/views/boondog.h"', | |
504 '#include "chrome/browser/ui/views/ash/panel_view_aura.h"']], | |
505 # This should fail because of no rule applying. | |
506 ['chrome/browser/lifetime/bongo.cc', ['#include "fantastic/food.h"']], | |
507 # The following two shouldn't fail as they are under a directory | |
508 # that is skipped for checking, in src/DEPS. | |
509 ['breakpad/fooblat.cc', ['#include "fantastic/food.h"']], | |
510 ['breakpad/bingo/bongo/fooblat.cc', ['#include "fantastic/food.h"']], | |
511 ]) | |
512 | |
513 for problem in problems: | |
514 print problem[0] | |
515 print problem[1] | |
516 print problem[2] | |
517 print | |
518 print | |
519 | |
520 | |
411 def GetGitSourceDirectory(root): | 521 def GetGitSourceDirectory(root): |
412 """Returns a set of the directories to be checked. | 522 """Returns a set of the directories to be checked. |
413 | 523 |
414 Args: | 524 Args: |
415 root: The repository root where .git directory exists. | 525 root: The repository root where .git directory exists. |
416 | 526 |
417 Returns: | 527 Returns: |
418 A set of directories which contain sources managed by git. | 528 A set of directories which contain sources managed by git. |
419 """ | 529 """ |
420 git_source_directory = set() | 530 git_source_directory = set() |
421 popen_out = os.popen("cd %s && git ls-files --full-name ." % | 531 popen_out = os.popen("cd %s && git ls-files --full-name ." % |
422 pipes.quote(root)) | 532 subprocess.list2cmdline([root])) |
423 for line in popen_out.readlines(): | 533 for line in popen_out.readlines(): |
424 dir_name = os.path.join(root, os.path.dirname(line)) | 534 dir_name = os.path.join(root, os.path.dirname(line)) |
425 # Add the directory as well as all the parent directories. | 535 # Add the directory as well as all the parent directories. |
426 while dir_name != root: | 536 while dir_name != root: |
427 git_source_directory.add(dir_name) | 537 git_source_directory.add(dir_name) |
428 dir_name = os.path.dirname(dir_name) | 538 dir_name = os.path.dirname(dir_name) |
429 git_source_directory.add(root) | 539 git_source_directory.add(root) |
430 return git_source_directory | 540 return git_source_directory |
431 | 541 |
432 | 542 |
543 def CommonSetup(base_dir=None): | |
544 """Setup that is in common between the main() entrypoint to this | |
545 script and the CheckAddedIncludes (presubmit check) entrypoint. | |
546 """ | |
547 global BASE_DIRECTORY | |
548 global GIT_SOURCE_DIRECTORY | |
549 | |
550 if base_dir: | |
551 BASE_DIRECTORY = base_dir | |
552 else: | |
553 BASE_DIRECTORY = os.path.abspath( | |
554 os.path.join(os.path.abspath(os.path.dirname(__file__)), "../..")) | |
555 | |
556 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): | |
557 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) | |
558 | |
559 | |
433 def PrintUsage(): | 560 def PrintUsage(): |
434 print """Usage: python checkdeps.py [--root <root>] [tocheck] | 561 print """Usage: python checkdeps.py [--root <root>] [tocheck] |
435 --root Specifies the repository root. This defaults to "../../.." relative | 562 --root Specifies the repository root. This defaults to "../../.." relative |
436 to the script file. This will be correct given the normal location | 563 to the script file. This will be correct given the normal location |
437 of the script in "<root>/tools/checkdeps". | 564 of the script in "<root>/tools/checkdeps". |
438 | 565 |
439 tocheck Specifies the directory, relative to root, to check. This defaults | 566 tocheck Specifies the directory, relative to root, to check. This defaults |
440 to "." so it checks everything. Only one level deep is currently | 567 to "." so it checks everything. Only one level deep is currently |
441 supported, so you can say "chrome" but not "chrome/browser". | 568 supported, so you can say "chrome" but not "chrome/browser". |
442 | 569 |
443 Examples: | 570 Examples: |
444 python checkdeps.py | 571 python checkdeps.py |
445 python checkdeps.py --root c:\\source chrome""" | 572 python checkdeps.py --root c:\\source chrome""" |
446 | 573 |
447 | 574 |
448 def checkdeps(options, args): | 575 def checkdeps(options, args): |
576 global BASE_DIRECTORY | |
449 global VERBOSE | 577 global VERBOSE |
450 if options.verbose: | 578 if options.verbose: |
451 VERBOSE = True | 579 VERBOSE = True |
452 | 580 |
453 # Optional base directory of the repository. | 581 # Optional base directory of the repository. |
454 global BASE_DIRECTORY | |
455 if not options.base_directory: | 582 if not options.base_directory: |
456 BASE_DIRECTORY = os.path.abspath( | 583 CommonSetup() |
457 os.path.join(os.path.abspath(os.path.dirname(__file__)), "../..")) | |
458 else: | 584 else: |
459 BASE_DIRECTORY = os.path.abspath(options.base_directory) | 585 CommonSetup(os.path.abspath(options.base_directory)) |
460 | 586 |
461 # Figure out which directory we have to check. | 587 # Figure out which directory we have to check. |
462 if len(args) == 0: | 588 if len(args) == 0: |
463 # No directory to check specified, use the repository root. | 589 # No directory to check specified, use the repository root. |
464 start_dir = BASE_DIRECTORY | 590 start_dir = BASE_DIRECTORY |
465 elif len(args) == 1: | 591 elif len(args) == 1: |
466 # Directory specified. Start here. It's supposed to be relative to the | 592 # Directory specified. Start here. It's supposed to be relative to the |
467 # base directory. | 593 # base directory. |
468 start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0])) | 594 start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0])) |
469 else: | 595 else: |
470 # More than one argument, we don't handle this. | 596 # More than one argument, we don't handle this. |
471 PrintUsage() | 597 PrintUsage() |
472 return 1 | 598 return 1 |
473 | 599 |
474 print "Using base directory:", BASE_DIRECTORY | 600 print "Using base directory:", BASE_DIRECTORY |
475 print "Checking:", start_dir | 601 print "Checking:", start_dir |
476 | 602 |
477 base_rules = Rules() | 603 base_rules = Rules() |
478 | |
479 # The base directory should be lower case from here on since it will be used | |
480 # for substring matching on the includes, and we compile on case-insensitive | |
481 # systems. Plus, we always use slashes here since the include parsing code | |
482 # will also normalize to slashes. | |
483 BASE_DIRECTORY = BASE_DIRECTORY.lower() | |
484 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") | |
485 start_dir = start_dir.replace("\\", "/") | |
486 | |
487 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): | |
488 global GIT_SOURCE_DIRECTORY | |
489 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) | |
490 | |
491 success = CheckDirectory(base_rules, start_dir) | 604 success = CheckDirectory(base_rules, start_dir) |
492 if not success: | 605 if not success: |
493 print "\nFAILED\n" | 606 print "\nFAILED\n" |
494 return 1 | 607 return 1 |
495 print "\nSUCCESS\n" | 608 print "\nSUCCESS\n" |
496 return 0 | 609 return 0 |
497 | 610 |
498 | 611 |
499 def main(): | 612 def main(): |
500 option_parser = optparse.OptionParser() | 613 option_parser = optparse.OptionParser() |
501 option_parser.add_option("", "--root", default="", dest="base_directory", | 614 option_parser.add_option("", "--root", default="", dest="base_directory", |
502 help='Specifies the repository root. This defaults ' | 615 help='Specifies the repository root. This defaults ' |
503 'to "../../.." relative to the script file, which ' | 616 'to "../../.." relative to the script file, which ' |
504 'will normally be the repository root.') | 617 'will normally be the repository root.') |
505 option_parser.add_option("-v", "--verbose", action="store_true", | 618 option_parser.add_option("-v", "--verbose", action="store_true", |
506 default=False, help="Print debug logging") | 619 default=False, help="Print debug logging") |
507 options, args = option_parser.parse_args() | 620 options, args = option_parser.parse_args() |
508 return checkdeps(options, args) | 621 if args and args[0] == "manualtest": |
622 return TestCheckAddedIncludes() | |
623 else: | |
624 return checkdeps(options, args) | |
509 | 625 |
510 | 626 |
511 if '__main__' == __name__: | 627 if '__main__' == __name__: |
512 sys.exit(main()) | 628 sys.exit(main()) |
OLD | NEW |