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