OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import optparse | 5 import argparse |
6 import logging | |
6 import os | 7 import os |
7 import re | 8 import re |
8 import shlex | 9 import shlex |
9 import subprocess | |
10 import sys | 10 import sys |
11 import xml.dom.minidom | |
11 | 12 |
12 from pylib import cmd_helper | 13 from pylib import cmd_helper |
13 from pylib import constants | 14 from pylib import constants |
14 | 15 |
15 | 16 |
16 def _PrintMessage(warnings, title, action, known_bugs_file): | 17 _FINDBUGS_HOME = os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', |
17 if warnings: | 18 'findbugs') |
18 print | 19 _FINDBUGS_JAR = os.path.join(_FINDBUGS_HOME, 'lib', 'findbugs.jar') |
19 print '*' * 80 | 20 _FINDBUGS_MAX_HEAP = 768 |
20 print '%s warnings.' % title | 21 _FINDBUGS_PLUGIN_PATH = os.path.join( |
21 print '%s %s' % (action, known_bugs_file) | 22 constants.DIR_SOURCE_ROOT, 'tools', 'android', 'findbugs_plugin', 'lib', |
22 print '-' * 80 | 23 'chromiumPlugin.jar') |
23 for warning in warnings: | |
24 print warning | |
25 print '-' * 80 | |
26 print | |
27 | 24 |
28 | 25 |
29 def _StripLineNumbers(current_warnings): | 26 def _ParseXmlResults(results_doc): |
30 re_line = r':\[line.*?\]$' | 27 warnings = set() |
31 return [re.sub(re_line, '', x) for x in current_warnings] | 28 for en in (n for n in results_doc.documentElement.childNodes |
29 if n.nodeType == xml.dom.Node.ELEMENT_NODE): | |
30 if en.tagName == 'BugInstance': | |
31 warnings.add(_ParseBugInstance(en)) | |
32 return warnings | |
32 | 33 |
33 | 34 |
34 def _DiffKnownWarnings(current_warnings_set, known_bugs_file): | 35 def _ParseBugInstance(node): |
35 if os.path.exists(known_bugs_file): | 36 bug = FindBugsWarning(node.getAttribute('type')) |
36 with open(known_bugs_file, 'r') as known_bugs: | 37 for c in (n for n in node.childNodes |
37 known_bugs_set = set(known_bugs.read().splitlines()) | 38 if n.nodeType == xml.dom.Node.ELEMENT_NODE): |
38 else: | 39 if c.tagName == 'Class': |
39 known_bugs_set = set() | 40 bug.class_name = c.getAttribute('classname') |
40 | 41 elif c.tagName == 'Method': |
41 new_warnings = current_warnings_set - known_bugs_set | 42 bug.method_name = c.getAttribute('name') |
42 _PrintMessage(sorted(new_warnings), 'New', 'Please fix, or perhaps add to', | 43 elif c.tagName == 'Field': |
43 known_bugs_file) | 44 bug.field_name = c.getAttribute('name') |
44 | 45 elif c.tagName == 'SourceLine': |
45 obsolete_warnings = known_bugs_set - current_warnings_set | 46 bug.file_name = c.getAttribute('sourcefile') |
46 _PrintMessage(sorted(obsolete_warnings), 'Obsolete', 'Please remove from', | 47 bug.start_line = int(c.getAttribute('start')) |
47 known_bugs_file) | 48 bug.end_line = int(c.getAttribute('end')) |
48 | 49 elif (c.tagName == 'ShortMessage' and len(c.childNodes) == 1 |
49 count = len(new_warnings) + len(obsolete_warnings) | 50 and c.childNodes[0].nodeType == xml.dom.Node.TEXT_NODE): |
50 if count: | 51 bug.message = c.childNodes[0].data |
51 print '*** %d FindBugs warning%s! ***' % (count, 's' * (count > 1)) | 52 return bug |
52 if len(new_warnings): | |
53 print '*** %d: new ***' % len(new_warnings) | |
54 if len(obsolete_warnings): | |
55 print '*** %d: obsolete ***' % len(obsolete_warnings) | |
56 print | |
57 print 'Alternatively, rebaseline with --rebaseline command option' | |
58 print | |
59 else: | |
60 print 'No new FindBugs warnings.' | |
61 print | |
62 return count | |
63 | 53 |
64 | 54 |
65 def _Rebaseline(current_warnings_set, known_bugs_file): | 55 class FindBugsWarning(object): |
66 with file(known_bugs_file, 'w') as known_bugs: | 56 |
67 for warning in sorted(current_warnings_set): | 57 def __init__(self, bug_type='', class_name='', end_line=0, field_name='', |
68 print >> known_bugs, warning | 58 file_name='', message='', method_name='', start_line=0): |
69 return 0 | 59 self.bug_type = bug_type |
60 self.class_name = class_name | |
61 self.end_line = end_line | |
62 self.field_name = field_name | |
63 self.file_name = file_name | |
64 self.message = message | |
65 self.method_name = method_name | |
66 self.start_line = start_line | |
67 | |
68 def __cmp__(self, other): | |
cjhopman
2015/03/12 19:22:43
These functions could probably all just work on se
jbudorick
2015/03/13 13:12:19
Fine with that for __eq__ (especially since I miss
cjhopman
2015/03/17 01:31:16
you could just explicitly compare 1-4 and then com
| |
69 return (cmp(self.file_name, other.file_name) | |
70 or cmp(self.start_line, other.start_line) | |
71 or cmp(self.end_line, other.end_line) | |
72 or cmp(self.bug_type, other.bug_type) | |
73 or cmp(self.class_name, other.class_name) | |
74 or cmp(self.field_name, other.field_name) | |
75 or cmp(self.method_name, other.method_name) | |
76 or cmp(self.message, other.message)) | |
77 | |
78 def __eq__(self, other): | |
79 return (self.bug_type == other.bug_type | |
80 and self.class_name == other.class_name | |
81 and self.end_line == other.end_line | |
82 and self.message == other.message | |
83 and self.method_name == other.method_name | |
84 and self.start_line == other.start_line) | |
85 | |
86 def __hash__(self): | |
87 return hash((self.bug_type, self.class_name, self.end_line, self.message, | |
88 self.method_name, self.start_line)) | |
89 | |
90 def __ne__(self, other): | |
91 return not self == other | |
92 | |
93 def __str__(self): | |
94 return '%s at %s: %s (%s)' % ( | |
95 '%s.%s' % (self.class_name, self.method_name or self.field_name), | |
96 '%s:%s' % ( | |
97 self.file_name, | |
98 str(self.start_line) | |
99 if self.start_line == self.end_line | |
100 else '%s-%s' % (self.start_line, self.end_line)), | |
101 self.message, self.bug_type) | |
70 | 102 |
71 | 103 |
72 def _GetChromeJars(release_version): | 104 def Run(exclude, classes_to_analyze, auxiliary_classes, output_file, |
73 version = 'Debug' | 105 findbug_args, jars): |
74 if release_version: | 106 """Run FindBugs. |
75 version = 'Release' | |
76 path = os.path.join(constants.DIR_SOURCE_ROOT, | |
77 os.environ.get('CHROMIUM_OUT_DIR', 'out'), | |
78 version, | |
79 'lib.java') | |
80 cmd = 'find %s -name "*.jar"' % path | |
81 out = cmd_helper.GetCmdOutput(shlex.split(cmd)) | |
82 out = [p for p in out.splitlines() if not p.endswith('.dex.jar')] | |
83 if not out: | |
84 print 'No classes found in %s' % path | |
85 return ' '.join(out) | |
86 | |
87 | |
88 def _Run(exclude, known_bugs, classes_to_analyze, auxiliary_classes, | |
89 rebaseline, release_version, findbug_args): | |
90 """Run the FindBugs. | |
91 | 107 |
92 Args: | 108 Args: |
93 exclude: the exclude xml file, refer to FindBugs's -exclude command option. | 109 exclude: the exclude xml file, refer to FindBugs's -exclude command option. |
94 known_bugs: the text file of known bugs. The bugs in it will not be | |
95 reported. | |
96 classes_to_analyze: the list of classes need to analyze, refer to FindBug's | 110 classes_to_analyze: the list of classes need to analyze, refer to FindBug's |
97 -onlyAnalyze command line option. | 111 -onlyAnalyze command line option. |
98 auxiliary_classes: the classes help to analyze, refer to FindBug's | 112 auxiliary_classes: the classes help to analyze, refer to FindBug's |
99 -auxclasspath command line option. | 113 -auxclasspath command line option. |
100 rebaseline: True if the known_bugs file needs rebaseline. | 114 output_file: An optional path to dump XML results to. |
101 release_version: True if the release version needs check, otherwise check | 115 findbug_args: A list of addtional command line options to pass to Findbugs. |
102 debug version. | |
103 findbug_args: addtional command line options needs pass to Findbugs. | |
104 """ | 116 """ |
117 system_classes = [ | |
118 os.path.join(constants.ANDROID_SDK_ROOT, 'platforms', | |
119 'android-%s' % constants.ANDROID_SDK_VERSION, 'android.jar') | |
cjhopman
2015/03/12 19:22:43
This path should probably be gotten from the build
jbudorick
2015/03/13 13:12:19
TODO added.
| |
120 ] | |
121 system_classes.extend(os.path.abspath(classes) | |
122 for classes in auxiliary_classes or []) | |
105 | 123 |
106 chrome_src = constants.DIR_SOURCE_ROOT | 124 cmd = ['java', |
107 sdk_root = constants.ANDROID_SDK_ROOT | 125 '-classpath', '%s:' % _FINDBUGS_JAR, |
108 sdk_version = constants.ANDROID_SDK_VERSION | 126 '-Xmx%dm' % _FINDBUGS_MAX_HEAP, |
127 '-Dfindbugs.home="%s"' % _FINDBUGS_HOME, | |
128 '-jar', _FINDBUGS_JAR, | |
129 '-textui', '-sortByClass', | |
130 '-pluginList', _FINDBUGS_PLUGIN_PATH, '-xml:withMessages',] | |
131 if system_classes: | |
132 cmd.extend(['-auxclasspath', ':'.join(system_classes)]) | |
133 if classes_to_analyze: | |
134 cmd.extend(['-onlyAnalyze', classes_to_analyze]) | |
135 if exclude: | |
136 cmd.extend(['-exclude', os.path.abspath(exclude)]) | |
137 if output_file: | |
138 cmd.extend(['-output', output_file]) | |
139 if findbug_args: | |
140 cmd.extend(findbug_args) | |
141 cmd.extend(os.path.abspath(j) for j in jars or []) | |
109 | 142 |
110 system_classes = [] | 143 if output_file: |
111 system_classes.append(os.path.join(sdk_root, 'platforms', | 144 cmd_helper.RunCmd(cmd) |
112 'android-%s' % sdk_version, 'android.jar')) | 145 results_doc = xml.dom.minidom.parse(output_file) |
113 if auxiliary_classes: | 146 else: |
114 for classes in auxiliary_classes: | 147 raw_out = cmd_helper.GetCmdOutput(cmd) |
115 system_classes.append(os.path.abspath(classes)) | 148 results_doc = xml.dom.minidom.parseString(raw_out) |
149 current_warnings_set = _ParseXmlResults(results_doc) | |
116 | 150 |
117 findbugs_javacmd = 'java' | 151 return (' '.join(cmd), current_warnings_set) |
118 findbugs_home = os.path.join(chrome_src, 'third_party', 'findbugs') | |
119 findbugs_jar = os.path.join(findbugs_home, 'lib', 'findbugs.jar') | |
120 findbugs_pathsep = ':' | |
121 findbugs_maxheap = '768' | |
122 | 152 |
123 cmd = '%s ' % findbugs_javacmd | |
124 cmd = '%s -classpath %s%s' % (cmd, findbugs_jar, findbugs_pathsep) | |
125 cmd = '%s -Xmx%sm ' % (cmd, findbugs_maxheap) | |
126 cmd = '%s -Dfindbugs.home="%s" ' % (cmd, findbugs_home) | |
127 cmd = '%s -jar %s ' % (cmd, findbugs_jar) | |
128 | |
129 cmd = '%s -textui -sortByClass ' % cmd | |
130 cmd = '%s -pluginList %s' % (cmd, os.path.join(chrome_src, 'tools', 'android', | |
131 'findbugs_plugin', 'lib', | |
132 'chromiumPlugin.jar')) | |
133 if len(system_classes): | |
134 cmd = '%s -auxclasspath %s ' % (cmd, ':'.join(system_classes)) | |
135 | |
136 if classes_to_analyze: | |
137 cmd = '%s -onlyAnalyze %s ' % (cmd, classes_to_analyze) | |
138 | |
139 if exclude: | |
140 cmd = '%s -exclude %s ' % (cmd, os.path.abspath(exclude)) | |
141 | |
142 if findbug_args: | |
143 cmd = '%s %s ' % (cmd, findbug_args) | |
144 | |
145 chrome_classes = _GetChromeJars(release_version) | |
146 if not chrome_classes: | |
147 return 1 | |
148 cmd = '%s %s ' % (cmd, chrome_classes) | |
149 | |
150 print | |
151 print '*' * 80 | |
152 print 'Command used to run findbugs:' | |
153 print cmd | |
154 print '*' * 80 | |
155 print | |
156 | |
157 proc = subprocess.Popen(shlex.split(cmd), | |
158 stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
159 out, _err = proc.communicate() | |
160 current_warnings_set = set(_StripLineNumbers(filter(None, out.splitlines()))) | |
161 | |
162 if rebaseline: | |
163 return _Rebaseline(current_warnings_set, known_bugs) | |
164 else: | |
165 return _DiffKnownWarnings(current_warnings_set, known_bugs) | |
166 | |
167 def Run(options): | |
168 exclude_file = None | |
169 known_bugs_file = None | |
170 | |
171 if options.exclude: | |
172 exclude_file = options.exclude | |
173 elif options.base_dir: | |
174 exclude_file = os.path.join(options.base_dir, 'findbugs_exclude.xml') | |
175 | |
176 if options.known_bugs: | |
177 known_bugs_file = options.known_bugs | |
178 elif options.base_dir: | |
179 known_bugs_file = os.path.join(options.base_dir, 'findbugs_known_bugs.txt') | |
180 | |
181 auxclasspath = None | |
182 if options.auxclasspath: | |
183 auxclasspath = options.auxclasspath.split(':') | |
184 return _Run(exclude_file, known_bugs_file, options.only_analyze, auxclasspath, | |
185 options.rebaseline, options.release_build, options.findbug_args) | |
186 | |
187 | |
188 def GetCommonParser(): | |
189 parser = optparse.OptionParser() | |
190 parser.add_option('-r', | |
191 '--rebaseline', | |
192 action='store_true', | |
193 dest='rebaseline', | |
194 help='Rebaseline known findbugs issues.') | |
195 | |
196 parser.add_option('-a', | |
197 '--auxclasspath', | |
198 action='store', | |
199 default=None, | |
200 dest='auxclasspath', | |
201 help='Set aux classpath for analysis.') | |
202 | |
203 parser.add_option('-o', | |
204 '--only-analyze', | |
205 action='store', | |
206 default=None, | |
207 dest='only_analyze', | |
208 help='Only analyze the given classes and packages.') | |
209 | |
210 parser.add_option('-e', | |
211 '--exclude', | |
212 action='store', | |
213 default=None, | |
214 dest='exclude', | |
215 help='Exclude bugs matching given filter.') | |
216 | |
217 parser.add_option('-k', | |
218 '--known-bugs', | |
219 action='store', | |
220 default=None, | |
221 dest='known_bugs', | |
222 help='Not report the bugs in the given file.') | |
223 | |
224 parser.add_option('-l', | |
225 '--release-build', | |
226 action='store_true', | |
227 dest='release_build', | |
228 help='Analyze release build instead of debug.') | |
229 | |
230 parser.add_option('-f', | |
231 '--findbug-args', | |
232 action='store', | |
233 default=None, | |
234 dest='findbug_args', | |
235 help='Additional findbug arguments.') | |
236 | |
237 parser.add_option('-b', | |
238 '--base-dir', | |
239 action='store', | |
240 default=None, | |
241 dest='base_dir', | |
242 help='Base directory for configuration file.') | |
243 | |
244 return parser | |
245 | |
246 | |
247 def main(): | |
248 parser = GetCommonParser() | |
249 options, _ = parser.parse_args() | |
250 | |
251 return Run(options) | |
252 | |
253 | |
254 if __name__ == '__main__': | |
255 sys.exit(main()) | |
OLD | NEW |