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 cmd = '%s -textui -sortByClass ' % os.path.join(chrome_src, 'third_party', 111 'findbugs', 'bin', 'findbugs') 112 cmd = '%s -pluginList %s' % (cmd, os.path.join(chrome_src, 'tools', 'android', 113 'findbugs_plugin', 'lib', 114 'chromiumPlugin.jar')) 115 if len(system_classes): 116 cmd = '%s -auxclasspath %s ' % (cmd, ':'.join(system_classes)) 117 118 if classes_to_analyze: 119 cmd = '%s -onlyAnalyze %s ' % (cmd, classes_to_analyze) 120 121 if exclude: 122 cmd = '%s -exclude %s ' % (cmd, os.path.abspath(exclude)) 123 124 if findbug_args: 125 cmd = '%s %s ' % (cmd, findbug_args) 126 127 128 chrome_classes = _GetChromeClasses(release_version) 129 if not chrome_classes: 130 return 1 131 cmd = '%s %s ' % (cmd, chrome_classes) 132 133 proc = subprocess.Popen(shlex.split(cmd), 134 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 135 out, err = proc.communicate() 136 current_warnings_set = set(_StripLineNumbers(filter(None, out.splitlines()))) 137 138 if rebaseline: 139 return _Rebaseline(current_warnings_set, known_bugs) 140 else: 141 return _DiffKnownWarnings(current_warnings_set, known_bugs) 142 143 def Run(options): 144 exclude_file = None 145 known_bugs_file = None 146 147 if options.exclude: 148 exclude_file = options.exclude 149 elif options.base_dir: 150 exclude_file = os.path.join(options.base_dir, 'findbugs_exclude.xml') 151 152 if options.known_bugs: 153 known_bugs_file = options.known_bugs 154 elif options.base_dir: 155 known_bugs_file = os.path.join(options.base_dir, 'findbugs_known_bugs.txt') 156 157 auxclasspath = None 158 if options.auxclasspath: 159 auxclasspath = options.auxclasspath.split(':') 160 return _Run(exclude_file, known_bugs_file, options.only_analyze, auxclasspath, 161 options.rebaseline, options.release_build, options.findbug_args) 162 163 164 def GetCommonParser(): 165 parser = optparse.OptionParser() 166 parser.add_option('-r', 167 '--rebaseline', 168 action='store_true', 169 dest='rebaseline', 170 help='Rebaseline known findbugs issues.') 171 172 parser.add_option('-a', 173 '--auxclasspath', 174 action='store', 175 default=None, 176 dest='auxclasspath', 177 help='Set aux classpath for analysis.') 178 179 parser.add_option('-o', 180 '--only-analyze', 181 action='store', 182 default=None, 183 dest='only_analyze', 184 help='Only analyze the given classes and packages.') 185 186 parser.add_option('-e', 187 '--exclude', 188 action='store', 189 default=None, 190 dest='exclude', 191 help='Exclude bugs matching given filter.') 192 193 parser.add_option('-k', 194 '--known-bugs', 195 action='store', 196 default=None, 197 dest='known_bugs', 198 help='Not report the bugs in the given file.') 199 200 parser.add_option('-l', 201 '--release-build', 202 action='store_true', 203 dest='release_build', 204 help='Analyze release build instead of debug.') 205 206 parser.add_option('-f', 207 '--findbug-args', 208 action='store', 209 default=None, 210 dest='findbug_args', 211 help='Additional findbug arguments.') 212 213 parser.add_option('-b', 214 '--base-dir', 215 action='store', 216 default=None, 217 dest='base_dir', 218 help='Base directory for configuration file.') 219 220 return parser 221 222 223 def main(argv): 224 parser = GetCommonParser() 225 options, _ = parser.parse_args() 226 227 return Run(options) 228 229 230 if __name__ == '__main__': 231 sys.exit(main(sys.argv)) 232