Home | History | Annotate | Download | only in checkdeps
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Checks Java files for illegal imports."""
      6 
      7 import codecs
      8 import os
      9 import re
     10 
     11 import results
     12 from rules import Rule
     13 
     14 
     15 class JavaChecker(object):
     16   """Import checker for Java files.
     17 
     18   The CheckFile method uses real filesystem paths, but Java imports work in
     19   terms of package names. To deal with this, we have an extra "prescan" pass
     20   that reads all the .java files and builds a mapping of class name -> filepath.
     21   In CheckFile, we convert each import statement into a real filepath, and check
     22   that against the rules in the DEPS files.
     23 
     24   Note that in Java you can always use classes in the same directory without an
     25   explicit import statement, so these imports can't be blocked with DEPS files.
     26   But that shouldn't be a problem, because same-package imports are pretty much
     27   always correct by definition. (If we find a case where this is *not* correct,
     28   it probably means the package is too big and needs to be split up.)
     29 
     30   Properties:
     31     _classmap: dict of fully-qualified Java class name -> filepath
     32   """
     33 
     34   EXTENSIONS = ['.java']
     35 
     36   def __init__(self, base_directory, verbose):
     37     self._base_directory = base_directory
     38     self._verbose = verbose
     39     self._classmap = {}
     40     self._PrescanFiles()
     41 
     42   def _PrescanFiles(self):
     43     for root, dirs, files in os.walk(self._base_directory):
     44       # Skip unwanted subdirectories. TODO(husky): it would be better to do
     45       # this via the skip_child_includes flag in DEPS files. Maybe hoist this
     46       # prescan logic into checkdeps.py itself?
     47       for d in dirs:
     48         # Skip hidden directories.
     49         if d.startswith('.'):
     50           dirs.remove(d)
     51         # Skip the "out" directory, as dealing with generated files is awkward.
     52         # We don't want paths like "out/Release/lib.java" in our DEPS files.
     53         # TODO(husky): We need some way of determining the "real" path to
     54         # a generated file -- i.e., where it would be in source control if
     55         # it weren't generated.
     56         if d == 'out':
     57           dirs.remove(d)
     58         # Skip third-party directories.
     59         if d == 'third_party':
     60           dirs.remove(d)
     61       for f in files:
     62         if f.endswith('.java'):
     63           self._PrescanFile(os.path.join(root, f))
     64 
     65   def _PrescanFile(self, filepath):
     66     if self._verbose:
     67       print 'Prescanning: ' + filepath
     68     with codecs.open(filepath, encoding='utf-8') as f:
     69       short_class_name, _ = os.path.splitext(os.path.basename(filepath))
     70       for line in f:
     71         for package in re.findall('^package ([\w\.]+);', line):
     72           full_class_name = package + '.' + short_class_name
     73           if full_class_name in self._classmap:
     74             print 'WARNING: multiple definitions of %s:' % full_class_name
     75             print '    ' + filepath
     76             print '    ' + self._classmap[full_class_name]
     77             print
     78           else:
     79             self._classmap[full_class_name] = filepath
     80           return
     81       print 'WARNING: no package definition found in %s' % filepath
     82 
     83   def CheckFile(self, rules, filepath):
     84     if self._verbose:
     85       print 'Checking: ' + filepath
     86 
     87     dependee_status = results.DependeeStatus(filepath)
     88     with codecs.open(filepath, encoding='utf-8') as f:
     89       for line in f:
     90         for clazz in re.findall('^import\s+(?:static\s+)?([\w\.]+)\s*;', line):
     91           if clazz not in self._classmap:
     92             # Importing a class from outside the Chromium tree. That's fine --
     93             # it's probably a Java or Android system class.
     94             continue
     95           include_path = os.path.relpath(
     96               self._classmap[clazz], self._base_directory)
     97           # Convert Windows paths to Unix style, as used in DEPS files.
     98           include_path = include_path.replace(os.path.sep, '/')
     99           rule = rules.RuleApplyingTo(include_path, filepath)
    100           if rule.allow == Rule.DISALLOW:
    101             dependee_status.AddViolation(
    102                 results.DependencyViolation(include_path, rule, rules))
    103         if '{' in line:
    104           # This is code, so we're finished reading imports for this file.
    105           break
    106 
    107     return dependee_status
    108