OLD | NEW |
---|---|
(Empty) | |
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 """Checker implementation that checks Java files. | |
6 | |
7 If you import this module it will register itself with checker.Factory. | |
8 """ | |
9 | |
10 import fileinput | |
11 import os | |
12 import re | |
13 import sys | |
14 | |
15 import checker | |
16 | |
17 | |
18 class JavaChecker(checker.Checker): | |
19 """Checker implementation for Java files. | |
20 | |
21 The Checker interface uses real filesystem paths, but Java imports work in | |
22 terms of package names. To deal with this, we have an extra "prescan" pass | |
23 that reads all the .java files and builds a mapping of class name -> filename. | |
24 In CheckFile, we convert each import statement into a real filename, and check | |
25 that against the rules in the DEPS files. | |
26 | |
27 Note that in Java you can always use classes in the same directory without an | |
28 explicit import statement, so these imports can't be blocked with DEPS files. | |
29 But that shouldn't be a problem, because same-package imports are pretty much | |
30 always correct by definition. (If we find a case where this is *not* correct, | |
31 it probably means the package is too big and needs to be split up.) | |
32 | |
33 Properties: | |
34 _classmap: dict of fully-qualified Java class name -> filename | |
35 """ | |
36 | |
M-A Ruel
2012/07/17 14:23:20
EXTENSIONS = [
'.java',
]
Iain Merrick
2012/07/17 15:55:08
Done.
| |
37 def __init__(self, *args): | |
38 super(JavaChecker, self).__init__(*args) | |
39 self._PrescanFiles() | |
40 | |
41 def _PrescanFiles(self): | |
42 self._classmap = {} | |
M-A Ruel
2012/07/17 14:23:20
Define it in the __init__ function. Make sure your
Iain Merrick
2012/07/17 15:55:08
Done.
| |
43 for root, dirs, files in os.walk(self._base_directory): | |
44 # Skip unwanted subdirectories. TODO(husky): it would be better to do this | |
45 # via the skip_child_includes flag in DEPS files. Maybe hoist this prescan | |
46 # 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, filename): | |
M-A Ruel
2012/07/17 14:23:20
It's really more a filepath than a filename, but I
Iain Merrick
2012/07/18 13:22:12
Changed (and I just noticed that my search-and-rep
| |
66 filename = os.path.relpath(filename, self._base_directory) | |
67 if self._verbose: | |
68 print 'Prescanning: ' + filename | |
69 f = fileinput.input(filename) | |
M-A Ruel
2012/07/17 14:23:20
I don't see why you are using fileinput at all her
Iain Merrick
2012/07/17 15:55:08
Just insufficient time spent searching the library
| |
70 try: | |
71 short_class_name, _ = os.path.splitext(os.path.basename(filename)) | |
M-A Ruel
2012/07/17 14:23:20
short_class_name = os.path.splitext(os.path.basena
| |
72 for line in f: | |
73 for package in re.findall('^package ([\w\.]+);', line): | |
74 full_class_name = package + '.' + short_class_name | |
75 if full_class_name in self._classmap: | |
76 print 'WARNING: multiple definitions of %s:' % full_class_name | |
77 print ' ' + filename | |
78 print ' ' + self._classmap[full_class_name] | |
79 print | |
80 else: | |
81 self._classmap[full_class_name] = filename | |
82 return | |
83 print 'WARNING: no package definition found in %s' % filename | |
84 finally: | |
85 f.close() | |
86 | |
87 def Extensions(self): | |
M-A Ruel
2012/07/17 14:23:20
Remove. This doesn't need to be a member function.
Iain Merrick
2012/07/17 15:55:08
Done.
| |
88 return ['.java'] | |
89 | |
90 def CheckFile(self, rules, filename): | |
91 if self._verbose: | |
92 print 'Checking: ' + filename | |
93 | |
94 result = '' | |
95 f = fileinput.input(filename) | |
M-A Ruel
2012/07/17 14:23:20
with fileinput.input(filename) as f:
Remove the f
Iain Merrick
2012/07/17 15:55:08
Done.
| |
96 try: | |
97 for line in f: | |
98 for clazz in re.findall('^import\s+(?:static\s+)?([\w\.]+)\s*;', line): | |
99 if clazz not in self._classmap: | |
100 # Importing a class from outside the Chromium tree. That's fine -- | |
101 # it's probably a Java or Android system class. | |
102 continue | |
103 include_path = self._classmap[clazz] | |
104 (allowed, why_failed) = rules.DirAllowed(include_path) | |
105 if not allowed: | |
106 if self._verbose: | |
107 result += '\nFor ' + rules.__str__() | |
108 result += 'Illegal include: "%s"\n Because of %s\n' % ( | |
109 include_path, why_failed) | |
110 if '{' in line: | |
111 # This is code, so we're finished reading imports for this file. | |
112 break | |
113 | |
114 except IOError: | |
Iain Merrick
2012/07/18 13:22:12
Note: I've removed all IOError handling, as this i
| |
115 if self._verbose: | |
116 print 'Unable to open file: ' + filename | |
117 | |
118 finally: | |
119 f.close() | |
120 | |
121 if len(result) == 0: | |
M-A Ruel
2012/07/17 14:23:20
if result:
return result
None is implicitly ret
Iain Merrick
2012/07/17 15:55:08
Done.
| |
122 return None | |
123 return result | |
124 | |
M-A Ruel
2012/07/17 14:23:20
Remove extra line.
Iain Merrick
2012/07/17 15:55:08
Done.
| |
OLD | NEW |