Index: build/android/pylib/utils/findbugs.py |
diff --git a/build/android/pylib/utils/findbugs.py b/build/android/pylib/utils/findbugs.py |
index 82408b017d85b219f1e8e5036b57c7dfb2901279..a9d7e15e6dcf3f30334c16cd449cc9e774f972ce 100644 |
--- a/build/android/pylib/utils/findbugs.py |
+++ b/build/android/pylib/utils/findbugs.py |
@@ -2,254 +2,151 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
-import optparse |
+import argparse |
+import logging |
import os |
import re |
import shlex |
-import subprocess |
import sys |
+import xml.dom.minidom |
from pylib import cmd_helper |
from pylib import constants |
-def _PrintMessage(warnings, title, action, known_bugs_file): |
- if warnings: |
- print '*' * 80 |
- print '%s warnings.' % title |
- print '%s %s' % (action, known_bugs_file) |
- print '-' * 80 |
- for warning in warnings: |
- print warning |
- print '-' * 80 |
- |
- |
-def _StripLineNumbers(current_warnings): |
- re_line = r':\[line.*?\]$' |
- return [re.sub(re_line, '', x) for x in current_warnings] |
- |
- |
-def _DiffKnownWarnings(current_warnings_set, known_bugs_file): |
- if os.path.exists(known_bugs_file): |
- with open(known_bugs_file, 'r') as known_bugs: |
- known_bugs_set = set(known_bugs.read().splitlines()) |
- else: |
- known_bugs_set = set() |
- |
- new_warnings = current_warnings_set - known_bugs_set |
- _PrintMessage(sorted(new_warnings), 'New', 'Please fix, or perhaps add to', |
- known_bugs_file) |
- |
- obsolete_warnings = known_bugs_set - current_warnings_set |
- _PrintMessage(sorted(obsolete_warnings), 'Obsolete', 'Please remove from', |
- known_bugs_file) |
- |
- count = len(new_warnings) + len(obsolete_warnings) |
- if count: |
- print '*** %d FindBugs warning%s! ***' % (count, 's' * (count > 1)) |
- if len(new_warnings): |
- print '*** %d: new ***' % len(new_warnings) |
- if len(obsolete_warnings): |
- print '*** %d: obsolete ***' % len(obsolete_warnings) |
- print 'Alternatively, rebaseline with --rebaseline command option' |
- else: |
- print 'No new FindBugs warnings.' |
- return count |
- |
- |
-def _Rebaseline(current_warnings_set, known_bugs_file): |
- with file(known_bugs_file, 'w') as known_bugs: |
- for warning in sorted(current_warnings_set): |
- print >> known_bugs, warning |
- return 0 |
- |
- |
-def _GetChromeJars(release_version): |
- version = 'Debug' |
- if release_version: |
- version = 'Release' |
- path = os.path.join(constants.DIR_SOURCE_ROOT, |
- os.environ.get('CHROMIUM_OUT_DIR', 'out'), |
- version, |
- 'lib.java') |
- cmd = 'find %s -name "*.jar"' % path |
- out = cmd_helper.GetCmdOutput(shlex.split(cmd)) |
- out = [p for p in out.splitlines() if not p.endswith('.dex.jar')] |
- if not out: |
- print 'No classes found in %s' % path |
- return ' '.join(out) |
- |
- |
-def _Run(exclude, known_bugs, classes_to_analyze, auxiliary_classes, |
- rebaseline, release_version, findbug_args): |
- """Run the FindBugs. |
+_FINDBUGS_HOME = os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', |
+ 'findbugs') |
+_FINDBUGS_JAR = os.path.join(_FINDBUGS_HOME, 'lib', 'findbugs.jar') |
+_FINDBUGS_MAX_HEAP = 768 |
+_FINDBUGS_PLUGIN_PATH = os.path.join( |
+ constants.DIR_SOURCE_ROOT, 'tools', 'android', 'findbugs_plugin', 'lib', |
+ 'chromiumPlugin.jar') |
+ |
+ |
+def _ParseXmlResults(results_doc): |
+ warnings = set() |
+ for en in (n for n in results_doc.documentElement.childNodes |
+ if n.nodeType == xml.dom.Node.ELEMENT_NODE): |
+ if en.tagName == 'BugInstance': |
+ warnings.add(_ParseBugInstance(en)) |
+ return warnings |
+ |
+ |
+def _ParseBugInstance(node): |
+ bug = FindBugsWarning(node.getAttribute('type')) |
+ for c in (n for n in node.childNodes |
+ if n.nodeType == xml.dom.Node.ELEMENT_NODE): |
+ if c.tagName == 'Class': |
+ bug.class_name = c.getAttribute('classname') |
+ elif c.tagName == 'Method': |
+ bug.method_name = c.getAttribute('name') |
+ elif c.tagName == 'Field': |
+ bug.field_name = c.getAttribute('name') |
+ elif c.tagName == 'SourceLine': |
+ bug.file_name = c.getAttribute('sourcefile') |
+ bug.start_line = int(c.getAttribute('start')) |
+ bug.end_line = int(c.getAttribute('end')) |
+ elif (c.tagName == 'ShortMessage' and len(c.childNodes) == 1 |
+ and c.childNodes[0].nodeType == xml.dom.Node.TEXT_NODE): |
+ bug.message = c.childNodes[0].data |
+ return bug |
+ |
+ |
+class FindBugsWarning(object): |
+ |
+ def __init__(self, bug_type='', class_name='', end_line=0, field_name='', |
+ file_name='', message='', method_name='', start_line=0): |
+ self.bug_type = bug_type |
+ self.class_name = class_name |
+ self.end_line = end_line |
+ self.field_name = field_name |
+ self.file_name = file_name |
+ self.message = message |
+ self.method_name = method_name |
+ self.start_line = start_line |
+ |
+ 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
|
+ return (cmp(self.file_name, other.file_name) |
+ or cmp(self.start_line, other.start_line) |
+ or cmp(self.end_line, other.end_line) |
+ or cmp(self.bug_type, other.bug_type) |
+ or cmp(self.class_name, other.class_name) |
+ or cmp(self.field_name, other.field_name) |
+ or cmp(self.method_name, other.method_name) |
+ or cmp(self.message, other.message)) |
+ |
+ def __eq__(self, other): |
+ return (self.bug_type == other.bug_type |
+ and self.class_name == other.class_name |
+ and self.end_line == other.end_line |
+ and self.message == other.message |
+ and self.method_name == other.method_name |
+ and self.start_line == other.start_line) |
+ |
+ def __hash__(self): |
+ return hash((self.bug_type, self.class_name, self.end_line, self.message, |
+ self.method_name, self.start_line)) |
+ |
+ def __ne__(self, other): |
+ return not self == other |
+ |
+ def __str__(self): |
+ return '%s at %s: %s (%s)' % ( |
+ '%s.%s' % (self.class_name, self.method_name or self.field_name), |
+ '%s:%s' % ( |
+ self.file_name, |
+ str(self.start_line) |
+ if self.start_line == self.end_line |
+ else '%s-%s' % (self.start_line, self.end_line)), |
+ self.message, self.bug_type) |
+ |
+ |
+def Run(exclude, classes_to_analyze, auxiliary_classes, output_file, |
+ findbug_args, jars): |
+ """Run FindBugs. |
Args: |
exclude: the exclude xml file, refer to FindBugs's -exclude command option. |
- known_bugs: the text file of known bugs. The bugs in it will not be |
- reported. |
classes_to_analyze: the list of classes need to analyze, refer to FindBug's |
-onlyAnalyze command line option. |
auxiliary_classes: the classes help to analyze, refer to FindBug's |
-auxclasspath command line option. |
- rebaseline: True if the known_bugs file needs rebaseline. |
- release_version: True if the release version needs check, otherwise check |
- debug version. |
- findbug_args: addtional command line options needs pass to Findbugs. |
+ output_file: An optional path to dump XML results to. |
+ findbug_args: A list of addtional command line options to pass to Findbugs. |
""" |
- |
- chrome_src = constants.DIR_SOURCE_ROOT |
- sdk_root = constants.ANDROID_SDK_ROOT |
- sdk_version = constants.ANDROID_SDK_VERSION |
- |
- system_classes = [] |
- system_classes.append(os.path.join(sdk_root, 'platforms', |
- 'android-%s' % sdk_version, 'android.jar')) |
- if auxiliary_classes: |
- for classes in auxiliary_classes: |
- system_classes.append(os.path.abspath(classes)) |
- |
- findbugs_javacmd = 'java' |
- findbugs_home = os.path.join(chrome_src, 'third_party', 'findbugs') |
- findbugs_jar = os.path.join(findbugs_home, 'lib', 'findbugs.jar') |
- findbugs_pathsep = ':' |
- findbugs_maxheap = '768' |
- |
- cmd = '%s ' % findbugs_javacmd |
- cmd = '%s -classpath %s%s' % (cmd, findbugs_jar, findbugs_pathsep) |
- cmd = '%s -Xmx%sm ' % (cmd, findbugs_maxheap) |
- cmd = '%s -Dfindbugs.home="%s" ' % (cmd, findbugs_home) |
- cmd = '%s -jar %s ' % (cmd, findbugs_jar) |
- |
- cmd = '%s -textui -sortByClass ' % cmd |
- cmd = '%s -pluginList %s' % (cmd, os.path.join(chrome_src, 'tools', 'android', |
- 'findbugs_plugin', 'lib', |
- 'chromiumPlugin.jar')) |
- if len(system_classes): |
- cmd = '%s -auxclasspath %s ' % (cmd, ':'.join(system_classes)) |
- |
+ system_classes = [ |
+ os.path.join(constants.ANDROID_SDK_ROOT, 'platforms', |
+ '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.
|
+ ] |
+ system_classes.extend(os.path.abspath(classes) |
+ for classes in auxiliary_classes or []) |
+ |
+ cmd = ['java', |
+ '-classpath', '%s:' % _FINDBUGS_JAR, |
+ '-Xmx%dm' % _FINDBUGS_MAX_HEAP, |
+ '-Dfindbugs.home="%s"' % _FINDBUGS_HOME, |
+ '-jar', _FINDBUGS_JAR, |
+ '-textui', '-sortByClass', |
+ '-pluginList', _FINDBUGS_PLUGIN_PATH, '-xml:withMessages',] |
+ if system_classes: |
+ cmd.extend(['-auxclasspath', ':'.join(system_classes)]) |
if classes_to_analyze: |
- cmd = '%s -onlyAnalyze %s ' % (cmd, classes_to_analyze) |
- |
+ cmd.extend(['-onlyAnalyze', classes_to_analyze]) |
if exclude: |
- cmd = '%s -exclude %s ' % (cmd, os.path.abspath(exclude)) |
- |
+ cmd.extend(['-exclude', os.path.abspath(exclude)]) |
+ if output_file: |
+ cmd.extend(['-output', output_file]) |
if findbug_args: |
- cmd = '%s %s ' % (cmd, findbug_args) |
- |
- chrome_classes = _GetChromeJars(release_version) |
- if not chrome_classes: |
- return 1 |
- cmd = '%s %s ' % (cmd, chrome_classes) |
- |
- print '*' * 80 |
- print 'Command used to run findbugs:' |
- print cmd |
- print '*' * 80 |
- |
- proc = subprocess.Popen(shlex.split(cmd), |
- stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
- out, _err = proc.communicate() |
- current_warnings_set = set(_StripLineNumbers(filter(None, out.splitlines()))) |
- |
- if rebaseline: |
- return _Rebaseline(current_warnings_set, known_bugs) |
+ cmd.extend(findbug_args) |
+ cmd.extend(os.path.abspath(j) for j in jars or []) |
+ |
+ if output_file: |
+ cmd_helper.RunCmd(cmd) |
+ results_doc = xml.dom.minidom.parse(output_file) |
else: |
- return _DiffKnownWarnings(current_warnings_set, known_bugs) |
- |
-def Run(options): |
- exclude_file = None |
- known_bugs_file = None |
- |
- if options.exclude: |
- exclude_file = options.exclude |
- elif options.base_dir: |
- exclude_file = os.path.join(options.base_dir, 'findbugs_exclude.xml') |
- |
- if options.known_bugs: |
- known_bugs_file = options.known_bugs |
- elif options.base_dir: |
- known_bugs_file = os.path.join(options.base_dir, 'findbugs_known_bugs.txt') |
- |
- auxclasspath = None |
- if options.auxclasspath: |
- auxclasspath = options.auxclasspath.split(':') |
- return _Run(exclude_file, known_bugs_file, options.only_analyze, auxclasspath, |
- options.rebaseline, options.release_build, options.findbug_args) |
- |
- |
-def GetCommonParser(): |
- parser = optparse.OptionParser() |
- parser.add_option('-r', |
- '--rebaseline', |
- action='store_true', |
- dest='rebaseline', |
- help='Rebaseline known findbugs issues.') |
- |
- parser.add_option('-a', |
- '--auxclasspath', |
- action='store', |
- default=None, |
- dest='auxclasspath', |
- help='Set aux classpath for analysis.') |
- |
- parser.add_option('-o', |
- '--only-analyze', |
- action='store', |
- default=None, |
- dest='only_analyze', |
- help='Only analyze the given classes and packages.') |
- |
- parser.add_option('-e', |
- '--exclude', |
- action='store', |
- default=None, |
- dest='exclude', |
- help='Exclude bugs matching given filter.') |
- |
- parser.add_option('-k', |
- '--known-bugs', |
- action='store', |
- default=None, |
- dest='known_bugs', |
- help='Not report the bugs in the given file.') |
- |
- parser.add_option('-l', |
- '--release-build', |
- action='store_true', |
- dest='release_build', |
- help='Analyze release build instead of debug.') |
- |
- parser.add_option('-f', |
- '--findbug-args', |
- action='store', |
- default=None, |
- dest='findbug_args', |
- help='Additional findbug arguments.') |
- |
- parser.add_option('-b', |
- '--base-dir', |
- action='store', |
- default=None, |
- dest='base_dir', |
- help='Base directory for configuration file.') |
- |
- return parser |
- |
- |
-def main(): |
- parser = GetCommonParser() |
- options, _ = parser.parse_args() |
- |
- return Run(options) |
- |
- |
-if __name__ == '__main__': |
- sys.exit(main()) |
+ raw_out = cmd_helper.GetCmdOutput(cmd) |
+ results_doc = xml.dom.minidom.parseString(raw_out) |
+ current_warnings_set = _ParseXmlResults(results_doc) |
+ |
+ return (' '.join(cmd), current_warnings_set) |
+ |