Home | History | Annotate | Download | only in utils
      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