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   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