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