#!/usr/bin/env python
#
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Runs Android's lint tool."""


import optparse
import os
import sys
from xml.dom import minidom

from util import build_utils


_SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),
                                         '..', '..', '..'))


def _RunLint(lint_path, config_path, processed_config_path, manifest_path,
             result_path, product_dir, src_dirs, classes_dir):

  def _RelativizePath(path):
    """Returns relative path to top-level src dir.

    Args:
      path: A path relative to cwd.
    """
    return os.path.relpath(os.path.abspath(path), _SRC_ROOT)

  def _ProcessConfigFile():
    if not build_utils.IsTimeStale(processed_config_path, [config_path]):
      return

    with open(config_path, 'rb') as f:
      content = f.read().replace(
          'PRODUCT_DIR', _RelativizePath(product_dir))

    with open(processed_config_path, 'wb') as f:
      f.write(content)

  def _ProcessResultFile():
    with open(result_path, 'rb') as f:
      content = f.read().replace(
          _RelativizePath(product_dir), 'PRODUCT_DIR')

    with open(result_path, 'wb') as f:
      f.write(content)

  def _ParseAndShowResultFile():
    dom = minidom.parse(result_path)
    issues = dom.getElementsByTagName('issue')
    print >> sys.stderr
    for issue in issues:
      issue_id = issue.attributes['id'].value
      severity = issue.attributes['severity'].value
      message = issue.attributes['message'].value
      location_elem = issue.getElementsByTagName('location')[0]
      path = location_elem.attributes['file'].value
      line = location_elem.getAttribute('line')
      if line:
        error = '%s:%s %s: %s [%s]' % (path, line, severity, message,
                                       issue_id)
      else:
        # Issues in class files don't have a line number.
        error = '%s %s: %s [%s]' % (path, severity, message, issue_id)
      print >> sys.stderr, error
      for attr in ['errorLine1', 'errorLine2']:
        error_line = issue.getAttribute(attr)
        if error_line:
          print >> sys.stderr, error_line
    return len(issues)

  _ProcessConfigFile()

  cmd = [
      lint_path, '-Werror', '--exitcode', '--showall',
      '--config', _RelativizePath(processed_config_path),
      '--classpath', _RelativizePath(classes_dir),
      '--xml', _RelativizePath(result_path),
  ]
  for src in src_dirs:
    cmd.extend(['--sources', _RelativizePath(src)])
  cmd.append(_RelativizePath(os.path.join(manifest_path, os.pardir)))

  if os.path.exists(result_path):
    os.remove(result_path)

  try:
    build_utils.CheckOutput(cmd, cwd=_SRC_ROOT)
  except build_utils.CalledProcessError:
    # There is a problem with lint usage
    if not os.path.exists(result_path):
      raise
    # There are actual lint issues
    else:
      num_issues = _ParseAndShowResultFile()
      _ProcessResultFile()
      msg = ('\nLint found %d new issues.\n'
             ' - For full explanation refer to %s\n'
             ' - Wanna suppress these issues?\n'
             '    1. Read comment in %s\n'
             '    2. Run "python %s %s"\n' %
             (num_issues,
              _RelativizePath(result_path),
              _RelativizePath(config_path),
              _RelativizePath(os.path.join(_SRC_ROOT, 'build', 'android',
                                           'lint', 'suppress.py')),
              _RelativizePath(result_path)))
      print >> sys.stderr, msg
      # Lint errors do not fail the build.
      return 0

  return 0


def main():
  parser = optparse.OptionParser()
  parser.add_option('--lint-path', help='Path to lint executable.')
  parser.add_option('--config-path', help='Path to lint suppressions file.')
  parser.add_option('--processed-config-path',
                    help='Path to processed lint suppressions file.')
  parser.add_option('--manifest-path', help='Path to AndroidManifest.xml')
  parser.add_option('--result-path', help='Path to XML lint result file.')
  parser.add_option('--product-dir', help='Path to product dir.')
  parser.add_option('--src-dirs', help='Directories containing java files.')
  parser.add_option('--classes-dir', help='Directory containing class files.')
  parser.add_option('--stamp', help='Path to touch on success.')
  parser.add_option('--enable', action='store_true',
                    help='Run lint instead of just touching stamp.')

  options, _ = parser.parse_args()

  build_utils.CheckOptions(
      options, parser, required=['lint_path', 'config_path',
                                 'processed_config_path', 'manifest_path',
                                 'result_path', 'product_dir', 'src_dirs',
                                 'classes_dir'])

  src_dirs = build_utils.ParseGypList(options.src_dirs)

  rc = 0

  if options.enable:
    rc = _RunLint(options.lint_path, options.config_path,
                  options.processed_config_path,
                  options.manifest_path, options.result_path,
                  options.product_dir, src_dirs, options.classes_dir)

  if options.stamp and not rc:
    build_utils.Touch(options.stamp)

  return rc


if __name__ == '__main__':
  sys.exit(main())

[((88, 5, 88, 32), 'os.path.exists', 'os.path.exists', ({(88, 20, 88, 31): 'result_path'}, {}), '(result_path)', False, 'import os\n'), ((120, 11, 120, 34), 'optparse.OptionParser', 'optparse.OptionParser', ({}, {}), '()', False, 'import optparse\n'), ((136, 2, 140, 48), 'util.build_utils.CheckOptions', 'build_utils.CheckOptions', (), '', False, 'from util import build_utils\n'), ((142, 13, 142, 55), 'util.build_utils.ParseGypList', 'build_utils.ParseGypList', ({(142, 38, 142, 54): 'options.src_dirs'}, {}), '(options.src_dirs)', False, 'from util import build_utils\n'), ((18, 41, 18, 66), 'os.path.dirname', 'os.path.dirname', ({(18, 57, 18, 65): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((53, 10, 53, 36), 'xml.dom.minidom.parse', 'minidom.parse', ({(53, 24, 53, 35): 'result_path'}, {}), '(result_path)', False, 'from xml.dom import minidom\n'), ((89, 4, 89, 26), 'os.remove', 'os.remove', ({(89, 14, 89, 25): 'result_path'}, {}), '(result_path)', False, 'import os\n'), ((92, 4, 92, 47), 'util.build_utils.CheckOutput', 'build_utils.CheckOutput', (), '', False, 'from util import build_utils\n'), ((153, 4, 153, 36), 'util.build_utils.Touch', 'build_utils.Touch', ({(153, 22, 153, 35): 'options.stamp'}, {}), '(options.stamp)', False, 'from util import build_utils\n'), ((31, 27, 31, 48), 'os.path.abspath', 'os.path.abspath', ({(31, 43, 31, 47): 'path'}, {}), '(path)', False, 'import os\n'), ((34, 11, 34, 72), 'util.build_utils.IsTimeStale', 'build_utils.IsTimeStale', ({(34, 35, 34, 56): 'processed_config_path', (34, 58, 34, 71): '[config_path]'}, {}), '(processed_config_path, [config_path])', False, 'from util import build_utils\n'), ((86, 29, 86, 67), 'os.path.join', 'os.path.join', ({(86, 42, 86, 55): 'manifest_path', (86, 57, 86, 66): 'os.pardir'}, {}), '(manifest_path, os.pardir)', False, 'import os\n'), ((95, 11, 95, 38), 'os.path.exists', 'os.path.exists', ({(95, 26, 95, 37): 'result_path'}, {}), '(result_path)', False, 'import os\n'), ((109, 30, 110, 65), 'os.path.join', 'os.path.join', ({(109, 43, 109, 52): '_SRC_ROOT', (109, 54, 109, 61): '"""build"""', (109, 63, 109, 72): '"""android"""', (110, 43, 110, 49): '"""lint"""', (110, 51, 110, 64): '"""suppress.py"""'}, {}), "(_SRC_ROOT, 'build', 'android', 'lint', 'suppress.py')", False, 'import os\n')]