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 5import optparse 6import os 7import re 8import shlex 9import subprocess 10import sys 11 12from pylib import cmd_helper 13from pylib import constants 14 15 16def _PrintMessage(warnings, title, action, known_bugs_file): 17 if warnings: 18 print 19 print '*' * 80 20 print '%s warnings.' % title 21 print '%s %s' % (action, known_bugs_file) 22 print '-' * 80 23 for warning in warnings: 24 print warning 25 print '-' * 80 26 print 27 28 29def _StripLineNumbers(current_warnings): 30 re_line = r':\[line.*?\]$' 31 return [re.sub(re_line, '', x) for x in current_warnings] 32 33 34def _DiffKnownWarnings(current_warnings_set, known_bugs_file): 35 with open(known_bugs_file, 'r') as known_bugs: 36 known_bugs_set = set(known_bugs.read().splitlines()) 37 38 new_warnings = current_warnings_set - known_bugs_set 39 _PrintMessage(sorted(new_warnings), 'New', 'Please fix, or perhaps add to', 40 known_bugs_file) 41 42 obsolete_warnings = known_bugs_set - current_warnings_set 43 _PrintMessage(sorted(obsolete_warnings), 'Obsolete', 'Please remove from', 44 known_bugs_file) 45 46 count = len(new_warnings) + len(obsolete_warnings) 47 if count: 48 print '*** %d FindBugs warning%s! ***' % (count, 's' * (count > 1)) 49 if len(new_warnings): 50 print '*** %d: new ***' % len(new_warnings) 51 if len(obsolete_warnings): 52 print '*** %d: obsolete ***' % len(obsolete_warnings) 53 print 54 print 'Alternatively, rebaseline with --rebaseline command option' 55 print 56 else: 57 print 'No new FindBugs warnings.' 58 print 59 return count 60 61 62def _Rebaseline(current_warnings_set, known_bugs_file): 63 with file(known_bugs_file, 'w') as known_bugs: 64 for warning in sorted(current_warnings_set): 65 print >> known_bugs, warning 66 return 0 67 68 69def _GetChromeJars(release_version): 70 version = 'Debug' 71 if release_version: 72 version = 'Release' 73 path = os.path.join(constants.DIR_SOURCE_ROOT, 74 os.environ.get('CHROMIUM_OUT_DIR', 'out'), 75 version, 76 'lib.java') 77 cmd = 'find %s -name "*.jar"' % path 78 out = cmd_helper.GetCmdOutput(shlex.split(cmd)) 79 out = [p for p in out.splitlines() if not p.endswith('.dex.jar')] 80 if not out: 81 print 'No classes found in %s' % path 82 return ' '.join(out) 83 84 85def _Run(exclude, known_bugs, classes_to_analyze, auxiliary_classes, 86 rebaseline, release_version, findbug_args): 87 """Run the FindBugs. 88 89 Args: 90 exclude: the exclude xml file, refer to FindBugs's -exclude command option. 91 known_bugs: the text file of known bugs. The bugs in it will not be 92 reported. 93 classes_to_analyze: the list of classes need to analyze, refer to FindBug's 94 -onlyAnalyze command line option. 95 auxiliary_classes: the classes help to analyze, refer to FindBug's 96 -auxclasspath command line option. 97 rebaseline: True if the known_bugs file needs rebaseline. 98 release_version: True if the release version needs check, otherwise check 99 debug version. 100 findbug_args: addtional command line options needs pass to Findbugs. 101 """ 102 103 chrome_src = constants.DIR_SOURCE_ROOT 104 sdk_root = constants.ANDROID_SDK_ROOT 105 sdk_version = constants.ANDROID_SDK_VERSION 106 107 system_classes = [] 108 system_classes.append(os.path.join(sdk_root, 'platforms', 109 'android-%s' % sdk_version, 'android.jar')) 110 if auxiliary_classes: 111 for classes in auxiliary_classes: 112 system_classes.append(os.path.abspath(classes)) 113 114 findbugs_javacmd = 'java' 115 findbugs_home = os.path.join(chrome_src, 'third_party', 'findbugs') 116 findbugs_jar = os.path.join(findbugs_home, 'lib', 'findbugs.jar') 117 findbugs_pathsep = ':' 118 findbugs_maxheap = '768' 119 120 cmd = '%s ' % findbugs_javacmd 121 cmd = '%s -classpath %s%s' % (cmd, findbugs_jar, findbugs_pathsep) 122 cmd = '%s -Xmx%sm ' % (cmd, findbugs_maxheap) 123 cmd = '%s -Dfindbugs.home="%s" ' % (cmd, findbugs_home) 124 cmd = '%s -jar %s ' % (cmd, findbugs_jar) 125 126 cmd = '%s -textui -sortByClass ' % cmd 127 cmd = '%s -pluginList %s' % (cmd, os.path.join(chrome_src, 'tools', 'android', 128 'findbugs_plugin', 'lib', 129 'chromiumPlugin.jar')) 130 if len(system_classes): 131 cmd = '%s -auxclasspath %s ' % (cmd, ':'.join(system_classes)) 132 133 if classes_to_analyze: 134 cmd = '%s -onlyAnalyze %s ' % (cmd, classes_to_analyze) 135 136 if exclude: 137 cmd = '%s -exclude %s ' % (cmd, os.path.abspath(exclude)) 138 139 if findbug_args: 140 cmd = '%s %s ' % (cmd, findbug_args) 141 142 chrome_classes = _GetChromeJars(release_version) 143 if not chrome_classes: 144 return 1 145 cmd = '%s %s ' % (cmd, chrome_classes) 146 147 proc = subprocess.Popen(shlex.split(cmd), 148 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 149 out, _err = proc.communicate() 150 current_warnings_set = set(_StripLineNumbers(filter(None, out.splitlines()))) 151 152 if rebaseline: 153 return _Rebaseline(current_warnings_set, known_bugs) 154 else: 155 return _DiffKnownWarnings(current_warnings_set, known_bugs) 156 157def Run(options): 158 exclude_file = None 159 known_bugs_file = None 160 161 if options.exclude: 162 exclude_file = options.exclude 163 elif options.base_dir: 164 exclude_file = os.path.join(options.base_dir, 'findbugs_exclude.xml') 165 166 if options.known_bugs: 167 known_bugs_file = options.known_bugs 168 elif options.base_dir: 169 known_bugs_file = os.path.join(options.base_dir, 'findbugs_known_bugs.txt') 170 171 auxclasspath = None 172 if options.auxclasspath: 173 auxclasspath = options.auxclasspath.split(':') 174 return _Run(exclude_file, known_bugs_file, options.only_analyze, auxclasspath, 175 options.rebaseline, options.release_build, options.findbug_args) 176 177 178def GetCommonParser(): 179 parser = optparse.OptionParser() 180 parser.add_option('-r', 181 '--rebaseline', 182 action='store_true', 183 dest='rebaseline', 184 help='Rebaseline known findbugs issues.') 185 186 parser.add_option('-a', 187 '--auxclasspath', 188 action='store', 189 default=None, 190 dest='auxclasspath', 191 help='Set aux classpath for analysis.') 192 193 parser.add_option('-o', 194 '--only-analyze', 195 action='store', 196 default=None, 197 dest='only_analyze', 198 help='Only analyze the given classes and packages.') 199 200 parser.add_option('-e', 201 '--exclude', 202 action='store', 203 default=None, 204 dest='exclude', 205 help='Exclude bugs matching given filter.') 206 207 parser.add_option('-k', 208 '--known-bugs', 209 action='store', 210 default=None, 211 dest='known_bugs', 212 help='Not report the bugs in the given file.') 213 214 parser.add_option('-l', 215 '--release-build', 216 action='store_true', 217 dest='release_build', 218 help='Analyze release build instead of debug.') 219 220 parser.add_option('-f', 221 '--findbug-args', 222 action='store', 223 default=None, 224 dest='findbug_args', 225 help='Additional findbug arguments.') 226 227 parser.add_option('-b', 228 '--base-dir', 229 action='store', 230 default=None, 231 dest='base_dir', 232 help='Base directory for configuration file.') 233 234 return parser 235 236 237def main(): 238 parser = GetCommonParser() 239 options, _ = parser.parse_args() 240 241 return Run(options) 242 243 244if __name__ == '__main__': 245 sys.exit(main()) 246