Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: tools/checkdeps/checkdeps.py

Issue 10790014: Add Java support to checkdeps.py (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Convert backslashes to slashes in Java include paths Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/checkdeps/cpp_checker.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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.
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
48 directory and then take away permissions from sub-parts, or the reverse. 48 directory and then take away permissions from sub-parts, or the reverse.
49 49
50 Note that all directory separators must be slashes (Unix-style) and not 50 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 51 backslashes. All directories should be relative to the source root and use
52 only lowercase. 52 only lowercase.
53 """ 53 """
54 54
55 import os 55 import os
56 import optparse 56 import optparse
57 import pipes 57 import pipes
58 import re
59 import sys 58 import sys
60 import copy 59 import copy
61 60
61 import cpp_checker
62 import java_checker
63
64
62 # Variable name used in the DEPS file to add or subtract include files from 65 # Variable name used in the DEPS file to add or subtract include files from
63 # the module-level deps. 66 # the module-level deps.
64 INCLUDE_RULES_VAR_NAME = "include_rules" 67 INCLUDE_RULES_VAR_NAME = "include_rules"
65 68
66 # Optionally present in the DEPS file to list subdirectories which should not 69 # 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. 70 # be checked. This allows us to skip third party code, for example.
68 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes" 71 SKIP_SUBDIRS_VAR_NAME = "skip_child_includes"
69 72
70 # The maximum number of non-include lines we can see before giving up.
71 MAX_UNINTERESTING_LINES = 50
72
73 # The maximum line length, this is to be efficient in the case of very long
74 # lines (which can't be #includes).
75 MAX_LINE_LENGTH = 128
76
77 # Set to true for more output. This is set by the command line options. 73 # Set to true for more output. This is set by the command line options.
78 VERBOSE = False 74 VERBOSE = False
79 75
80 # This regular expression will be used to extract filenames from include
81 # statements.
82 EXTRACT_INCLUDE_PATH = re.compile('[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"')
83
84 # In lowercase, using forward slashes as directory separators, ending in a 76 # In lowercase, using forward slashes as directory separators, ending in a
85 # forward slash. Set by the command line options. 77 # forward slash. Set by the command line options.
86 BASE_DIRECTORY = "" 78 BASE_DIRECTORY = ""
87 79
88 # The directories which contain the sources managed by git. 80 # The directories which contain the sources managed by git.
89 GIT_SOURCE_DIRECTORY = set() 81 GIT_SOURCE_DIRECTORY = set()
90 82
91 83
92 # Specifies a single rule for an include, which can be either allow or disallow. 84 # Specifies a single rule for an include, which can be either allow or disallow.
93 class Rule(object): 85 class Rule(object):
94 def __init__(self, allow, dir, source): 86 def __init__(self, allow, directory, source):
95 self._allow = allow 87 self.allow = allow
96 self._dir = dir 88 self._dir = directory
97 self._source = source 89 self._source = source
98 90
99 def __str__(self): 91 def __str__(self):
100 if (self._allow): 92 if (self.allow):
101 return '"+%s" from %s.' % (self._dir, self._source) 93 return '"+%s" from %s.' % (self._dir, self._source)
102 return '"-%s" from %s.' % (self._dir, self._source) 94 return '"-%s" from %s.' % (self._dir, self._source)
103 95
104 def ParentOrMatch(self, other): 96 def ParentOrMatch(self, other):
105 """Returns true if the input string is an exact match or is a parent 97 """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".""" 98 of the current rule. For example, the input "foo" would match "foo/bar"."""
107 return self._dir == other or self._dir.startswith(other + "/") 99 return self._dir == other or self._dir.startswith(other + "/")
108 100
109 def ChildOrMatch(self, other): 101 def ChildOrMatch(self, other):
110 """Returns true if the input string would be covered by this rule. For 102 """Returns true if the input string would be covered by this rule. For
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
153 self._rules = [x for x in self._rules if not x.ParentOrMatch(rule_dir)] 145 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)) 146 self._rules.insert(0, Rule(add_rule, rule_dir, source))
155 147
156 def DirAllowed(self, allowed_dir): 148 def DirAllowed(self, allowed_dir):
157 """Returns a tuple (success, message), where success indicates if the given 149 """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 150 directory is allowed given the current set of rules, and the message tells
159 why if the comparison failed.""" 151 why if the comparison failed."""
160 for rule in self._rules: 152 for rule in self._rules:
161 if rule.ChildOrMatch(allowed_dir): 153 if rule.ChildOrMatch(allowed_dir):
162 # This rule applies. 154 # This rule applies.
163 if rule._allow: 155 if rule.allow:
164 return (True, "") 156 return (True, "")
165 return (False, rule.__str__()) 157 return (False, rule.__str__())
166 # No rules apply, fail. 158 # No rules apply, fail.
167 return (False, "no rule applying") 159 return (False, "no rule applying")
168 160
169 161
170 def ApplyRules(existing_rules, includes, cur_dir): 162 def ApplyRules(existing_rules, includes, cur_dir):
171 """Applies the given include rules, returning the new rules. 163 """Applies the given include rules, returning the new rules.
172 164
173 Args: 165 Args:
(...skipping 15 matching lines...) Expand all
189 source = relative_dir 181 source = relative_dir
190 if len(source) == 0: 182 if len(source) == 0:
191 source = "top level" # Make the help string a little more meaningful. 183 source = "top level" # Make the help string a little more meaningful.
192 rules.AddRule("+" + relative_dir, "Default rule for " + source) 184 rules.AddRule("+" + relative_dir, "Default rule for " + source)
193 else: 185 else:
194 raise Exception("Internal error: base directory is not at the beginning" + 186 raise Exception("Internal error: base directory is not at the beginning" +
195 " for\n %s and base dir\n %s" % 187 " for\n %s and base dir\n %s" %
196 (cur_dir, BASE_DIRECTORY)) 188 (cur_dir, BASE_DIRECTORY))
197 189
198 # Last, apply the additional explicit rules. 190 # Last, apply the additional explicit rules.
199 for (index, rule_str) in enumerate(includes): 191 for (_, rule_str) in enumerate(includes):
200 if not len(relative_dir): 192 if not len(relative_dir):
201 rule_description = "the top level include_rules" 193 rule_description = "the top level include_rules"
202 else: 194 else:
203 rule_description = relative_dir + "'s include_rules" 195 rule_description = relative_dir + "'s include_rules"
204 rules.AddRule(rule_str, rule_description) 196 rules.AddRule(rule_str, rule_description)
205 197
206 return rules 198 return rules
207 199
208 200
209 def ApplyDirectoryRules(existing_rules, dir_name): 201 def ApplyDirectoryRules(existing_rules, dir_name):
(...skipping 16 matching lines...) Expand all
226 # Check for a .svn directory in this directory or check this directory is 218 # 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 219 # contained in git source direcotries. This will tell us if it's a source
228 # directory and should be checked. 220 # directory and should be checked.
229 if not (os.path.exists(os.path.join(dir_name, ".svn")) or 221 if not (os.path.exists(os.path.join(dir_name, ".svn")) or
230 (dir_name.lower() in GIT_SOURCE_DIRECTORY)): 222 (dir_name.lower() in GIT_SOURCE_DIRECTORY)):
231 return (None, []) 223 return (None, [])
232 224
233 # Check the DEPS file in this directory. 225 # Check the DEPS file in this directory.
234 if VERBOSE: 226 if VERBOSE:
235 print "Applying rules from", dir_name 227 print "Applying rules from", dir_name
236 def FromImpl(unused, unused2): 228 def FromImpl(_unused, _unused2):
237 pass # NOP function so "From" doesn't fail. 229 pass # NOP function so "From" doesn't fail.
238 230
239 def FileImpl(unused): 231 def FileImpl(_unused):
240 pass # NOP function so "File" doesn't fail. 232 pass # NOP function so "File" doesn't fail.
241 233
242 class _VarImpl: 234 class _VarImpl:
243 def __init__(self, local_scope): 235 def __init__(self, local_scope):
244 self._local_scope = local_scope 236 self._local_scope = local_scope
245 237
246 def Lookup(self, var_name): 238 def Lookup(self, var_name):
247 """Implements the Var syntax.""" 239 """Implements the Var syntax."""
248 if var_name in self._local_scope.get("vars", {}): 240 if var_name in self._local_scope.get("vars", {}):
249 return self._local_scope["vars"][var_name] 241 return self._local_scope["vars"][var_name]
250 raise Error("Var is not defined: %s" % var_name) 242 raise Exception("Var is not defined: %s" % var_name)
251 243
252 local_scope = {} 244 local_scope = {}
253 global_scope = { 245 global_scope = {
254 "File": FileImpl, 246 "File": FileImpl,
255 "From": FromImpl, 247 "From": FromImpl,
256 "Var": _VarImpl(local_scope).Lookup, 248 "Var": _VarImpl(local_scope).Lookup,
257 } 249 }
258 deps_file = os.path.join(dir_name, "DEPS") 250 deps_file = os.path.join(dir_name, "DEPS")
259 251
260 if os.path.isfile(deps_file): 252 if os.path.isfile(deps_file):
261 execfile(deps_file, global_scope, local_scope) 253 execfile(deps_file, global_scope, local_scope)
262 elif VERBOSE: 254 elif VERBOSE:
263 print " No deps file found in", dir_name 255 print " No deps file found in", dir_name
264 256
265 # Even if a DEPS file does not exist we still invoke ApplyRules 257 # Even if a DEPS file does not exist we still invoke ApplyRules
266 # to apply the implicit "allow" rule for the current directory 258 # to apply the implicit "allow" rule for the current directory
267 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) 259 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, [])
268 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) 260 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, [])
269 261
270 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs) 262 return (ApplyRules(existing_rules, include_rules, dir_name), skip_subdirs)
271 263
272 264
273 def ShouldCheckFile(file_name): 265 def CheckDirectory(parent_rules, checkers, dir_name):
274 """Returns True if the given file is a type we want to check."""
275 checked_extensions = [
276 '.h',
277 '.cc',
278 '.m',
279 '.mm',
280 ]
281 basename, extension = os.path.splitext(file_name)
282 return extension in checked_extensions
283
284
285 def CheckLine(rules, line):
286 """Checks the given file with the given rule set.
287 Returns a tuple (is_include, illegal_description).
288 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
290 the error. Otherwise, it will be None."""
291 found_item = EXTRACT_INCLUDE_PATH.match(line)
292 if not found_item:
293 return False, None # Not a match
294
295 include_path = found_item.group(1)
296
297 # Fix up backslashes in case somebody accidentally used them.
298 include_path.replace("\\", "/")
299
300 if include_path.find("/") < 0:
301 # Don't fail when no directory is specified. We may want to be more
302 # strict about this in the future.
303 if VERBOSE:
304 print " WARNING: directory specified with no path: " + include_path
305 return True, None
306
307 (allowed, why_failed) = rules.DirAllowed(include_path)
308 if not allowed:
309 if VERBOSE:
310 retval = "\nFor " + rules.__str__()
311 else:
312 retval = ""
313 return True, retval + ('Illegal include: "%s"\n Because of %s' %
314 (include_path, why_failed))
315
316 return True, None
317
318
319 def CheckFile(rules, file_name):
320 """Checks the given file with the given rule set.
321
322 Args:
323 rules: The set of rules that apply to files in this directory.
324 file_name: The source file to check.
325
326 Returns: Either a string describing the error if there was one, or None if
327 the file checked out OK.
328 """
329 if VERBOSE:
330 print "Checking: " + file_name
331
332 ret_val = "" # We'll collect the error messages in here
333 last_include = 0
334 try:
335 cur_file = open(file_name, "r")
336 in_if0 = 0
337 for line_num in xrange(sys.maxint):
338 if line_num - last_include > MAX_UNINTERESTING_LINES:
339 break
340
341 cur_line = cur_file.readline(MAX_LINE_LENGTH)
342 if cur_line == "":
343 break
344 cur_line = cur_line.strip()
345
346 # Check to see if we're at / inside a #if 0 block
347 if cur_line == '#if 0':
348 in_if0 += 1
349 continue
350 if in_if0 > 0:
351 if cur_line.startswith('#if'):
352 in_if0 += 1
353 elif cur_line == '#endif':
354 in_if0 -= 1
355 continue
356
357 is_include, line_status = CheckLine(rules, cur_line)
358 if is_include:
359 last_include = line_num
360 if line_status is not None:
361 if len(line_status) > 0: # Add newline to separate messages.
362 line_status += "\n"
363 ret_val += line_status
364 cur_file.close()
365
366 except IOError:
367 if VERBOSE:
368 print "Unable to open file: " + file_name
369 cur_file.close()
370
371 # Map empty string to None for easier checking.
372 if len(ret_val) == 0:
373 return None
374 return ret_val
375
376
377 def CheckDirectory(parent_rules, dir_name):
378 (rules, skip_subdirs) = ApplyDirectoryRules(parent_rules, dir_name) 266 (rules, skip_subdirs) = ApplyDirectoryRules(parent_rules, dir_name)
379 if rules == None: 267 if rules == None:
380 return True 268 return True
381 269
382 # Collect a list of all files and directories to check. 270 # Collect a list of all files and directories to check.
383 files_to_check = [] 271 files_to_check = []
384 dirs_to_check = [] 272 dirs_to_check = []
385 success = True 273 success = True
386 contents = os.listdir(dir_name) 274 contents = os.listdir(dir_name)
387 for cur in contents: 275 for cur in contents:
388 if cur in skip_subdirs: 276 if cur in skip_subdirs:
389 continue # Don't check children that DEPS has asked us to skip. 277 continue # Don't check children that DEPS has asked us to skip.
390 full_name = os.path.join(dir_name, cur) 278 full_name = os.path.join(dir_name, cur)
391 if os.path.isdir(full_name): 279 if os.path.isdir(full_name):
392 dirs_to_check.append(full_name) 280 dirs_to_check.append(full_name)
393 elif ShouldCheckFile(full_name): 281 elif os.path.splitext(full_name)[1] in checkers:
394 files_to_check.append(full_name) 282 files_to_check.append(full_name)
395 283
396 # First check all files in this directory. 284 # First check all files in this directory.
397 for cur in files_to_check: 285 for cur in files_to_check:
398 file_status = CheckFile(rules, cur) 286 checker = checkers[os.path.splitext(cur)[1]]
399 if file_status != None: 287 file_status = checker.CheckFile(rules, cur)
288 if file_status:
400 print "ERROR in " + cur + "\n" + file_status 289 print "ERROR in " + cur + "\n" + file_status
401 success = False 290 success = False
402 291
403 # Next recurse into the subdirectories. 292 # Next recurse into the subdirectories.
404 for cur in dirs_to_check: 293 for cur in dirs_to_check:
405 if not CheckDirectory(rules, cur): 294 if not CheckDirectory(rules, checkers, cur):
406 success = False 295 success = False
407 296
408 return success 297 return success
409 298
410 299
411 def GetGitSourceDirectory(root): 300 def GetGitSourceDirectory(root):
412 """Returns a set of the directories to be checked. 301 """Returns a set of the directories to be checked.
413 302
414 Args: 303 Args:
415 root: The repository root where .git directory exists. 304 root: The repository root where .git directory exists.
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
481 # systems. Plus, we always use slashes here since the include parsing code 370 # systems. Plus, we always use slashes here since the include parsing code
482 # will also normalize to slashes. 371 # will also normalize to slashes.
483 BASE_DIRECTORY = BASE_DIRECTORY.lower() 372 BASE_DIRECTORY = BASE_DIRECTORY.lower()
484 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/") 373 BASE_DIRECTORY = BASE_DIRECTORY.replace("\\", "/")
485 start_dir = start_dir.replace("\\", "/") 374 start_dir = start_dir.replace("\\", "/")
486 375
487 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")): 376 if os.path.exists(os.path.join(BASE_DIRECTORY, ".git")):
488 global GIT_SOURCE_DIRECTORY 377 global GIT_SOURCE_DIRECTORY
489 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) 378 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY)
490 379
491 success = CheckDirectory(base_rules, start_dir) 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)
492 if not success: 386 if not success:
493 print "\nFAILED\n" 387 print "\nFAILED\n"
494 return 1 388 return 1
495 print "\nSUCCESS\n" 389 print "\nSUCCESS\n"
496 return 0 390 return 0
497 391
498 392
499 def main(): 393 def main():
500 option_parser = optparse.OptionParser() 394 option_parser = optparse.OptionParser()
501 option_parser.add_option("", "--root", default="", dest="base_directory", 395 option_parser.add_option("", "--root", default="", dest="base_directory",
502 help='Specifies the repository root. This defaults ' 396 help='Specifies the repository root. This defaults '
503 'to "../../.." relative to the script file, which ' 397 'to "../../.." relative to the script file, which '
504 'will normally be the repository root.') 398 'will normally be the repository root.')
505 option_parser.add_option("-v", "--verbose", action="store_true", 399 option_parser.add_option("-v", "--verbose", action="store_true",
506 default=False, help="Print debug logging") 400 default=False, help="Print debug logging")
507 options, args = option_parser.parse_args() 401 options, args = option_parser.parse_args()
508 return checkdeps(options, args) 402 return checkdeps(options, args)
509 403
510 404
511 if '__main__' == __name__: 405 if '__main__' == __name__:
512 sys.exit(main()) 406 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | tools/checkdeps/cpp_checker.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698