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