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

Side by Side Diff: build/android/pylib/utils/findbugs.py

Issue 1000793002: [Android] Incorporate findbugs into android builds. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 9 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
OLDNEW
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())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698