Home | History | Annotate | Download | only in gyp
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (c) 2013 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 """Runs Android's lint tool."""
      8 
      9 
     10 import optparse
     11 import os
     12 import sys
     13 from xml.dom import minidom
     14 
     15 from util import build_utils
     16 
     17 
     18 _SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),
     19                                          '..', '..', '..'))
     20 
     21 
     22 def _RunLint(lint_path, config_path, processed_config_path, manifest_path,
     23              result_path, product_dir, src_dirs, classes_dir):
     24 
     25   def _RelativizePath(path):
     26     """Returns relative path to top-level src dir.
     27 
     28     Args:
     29       path: A path relative to cwd.
     30     """
     31     return os.path.relpath(os.path.abspath(path), _SRC_ROOT)
     32 
     33   def _ProcessConfigFile():
     34     if not build_utils.IsTimeStale(processed_config_path, [config_path]):
     35       return
     36 
     37     with open(config_path, 'rb') as f:
     38       content = f.read().replace(
     39           'PRODUCT_DIR', _RelativizePath(product_dir))
     40 
     41     with open(processed_config_path, 'wb') as f:
     42       f.write(content)
     43 
     44   def _ProcessResultFile():
     45     with open(result_path, 'rb') as f:
     46       content = f.read().replace(
     47           _RelativizePath(product_dir), 'PRODUCT_DIR')
     48 
     49     with open(result_path, 'wb') as f:
     50       f.write(content)
     51 
     52   def _ParseAndShowResultFile():
     53     dom = minidom.parse(result_path)
     54     issues = dom.getElementsByTagName('issue')
     55     print >> sys.stderr
     56     for issue in issues:
     57       issue_id = issue.attributes['id'].value
     58       severity = issue.attributes['severity'].value
     59       message = issue.attributes['message'].value
     60       location_elem = issue.getElementsByTagName('location')[0]
     61       path = location_elem.attributes['file'].value
     62       line = location_elem.getAttribute('line')
     63       if line:
     64         error = '%s:%s %s: %s [%s]' % (path, line, severity, message,
     65                                        issue_id)
     66       else:
     67         # Issues in class files don't have a line number.
     68         error = '%s %s: %s [%s]' % (path, severity, message, issue_id)
     69       print >> sys.stderr, error
     70       for attr in ['errorLine1', 'errorLine2']:
     71         error_line = issue.getAttribute(attr)
     72         if error_line:
     73           print >> sys.stderr, error_line
     74     return len(issues)
     75 
     76   _ProcessConfigFile()
     77 
     78   cmd = [
     79       lint_path, '-Werror', '--exitcode', '--showall',
     80       '--config', _RelativizePath(processed_config_path),
     81       '--classpath', _RelativizePath(classes_dir),
     82       '--xml', _RelativizePath(result_path),
     83   ]
     84   for src in src_dirs:
     85     cmd.extend(['--sources', _RelativizePath(src)])
     86   cmd.append(_RelativizePath(os.path.join(manifest_path, os.pardir)))
     87 
     88   if os.path.exists(result_path):
     89     os.remove(result_path)
     90 
     91   try:
     92     build_utils.CheckOutput(cmd, cwd=_SRC_ROOT)
     93   except build_utils.CalledProcessError:
     94     # There is a problem with lint usage
     95     if not os.path.exists(result_path):
     96       raise
     97     # There are actual lint issues
     98     else:
     99       num_issues = _ParseAndShowResultFile()
    100       _ProcessResultFile()
    101       msg = ('\nLint found %d new issues.\n'
    102              ' - For full explanation refer to %s\n'
    103              ' - Wanna suppress these issues?\n'
    104              '    1. Read comment in %s\n'
    105              '    2. Run "python %s %s"\n' %
    106              (num_issues,
    107               _RelativizePath(result_path),
    108               _RelativizePath(config_path),
    109               _RelativizePath(os.path.join(_SRC_ROOT, 'build', 'android',
    110                                            'lint', 'suppress.py')),
    111               _RelativizePath(result_path)))
    112       print >> sys.stderr, msg
    113       # Lint errors do not fail the build.
    114       return 0
    115 
    116   return 0
    117 
    118 
    119 def main():
    120   parser = optparse.OptionParser()
    121   parser.add_option('--lint-path', help='Path to lint executable.')
    122   parser.add_option('--config-path', help='Path to lint suppressions file.')
    123   parser.add_option('--processed-config-path',
    124                     help='Path to processed lint suppressions file.')
    125   parser.add_option('--manifest-path', help='Path to AndroidManifest.xml')
    126   parser.add_option('--result-path', help='Path to XML lint result file.')
    127   parser.add_option('--product-dir', help='Path to product dir.')
    128   parser.add_option('--src-dirs', help='Directories containing java files.')
    129   parser.add_option('--classes-dir', help='Directory containing class files.')
    130   parser.add_option('--stamp', help='Path to touch on success.')
    131   parser.add_option('--enable', action='store_true',
    132                     help='Run lint instead of just touching stamp.')
    133 
    134   options, _ = parser.parse_args()
    135 
    136   build_utils.CheckOptions(
    137       options, parser, required=['lint_path', 'config_path',
    138                                  'processed_config_path', 'manifest_path',
    139                                  'result_path', 'product_dir', 'src_dirs',
    140                                  'classes_dir'])
    141 
    142   src_dirs = build_utils.ParseGypList(options.src_dirs)
    143 
    144   rc = 0
    145 
    146   if options.enable:
    147     rc = _RunLint(options.lint_path, options.config_path,
    148                   options.processed_config_path,
    149                   options.manifest_path, options.result_path,
    150                   options.product_dir, src_dirs, options.classes_dir)
    151 
    152   if options.stamp and not rc:
    153     build_utils.Touch(options.stamp)
    154 
    155   return rc
    156 
    157 
    158 if __name__ == '__main__':
    159   sys.exit(main())
    160