Chromium Code Reviews| 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 |