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