OLD | NEW |
(Empty) | |
| 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 import tempfile |
| 14 from xml.dom import minidom |
| 15 |
| 16 from pylib import cmd_helper |
| 17 from pylib import constants |
| 18 |
| 19 |
| 20 # Dirs relative to /src to exclude from source linting. |
| 21 _EXCLUDE_SRC_DIRS = [ |
| 22 'third_party', |
| 23 'out', |
| 24 ] |
| 25 # Dirs relative to out/<type> to exclude from class linting. |
| 26 _EXCLUDE_CLASSES_DIRS = [ |
| 27 'gen/eyesfree_java', |
| 28 ] |
| 29 _LINT_EXE = os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'lint') |
| 30 _DOC = ('\nSTOP! It looks like you want to suppress some lint errors:\n' |
| 31 '- Have you tried identifing the offending patch?\n' |
| 32 ' Ask the author for a fix and/or revert the patch.\n' |
| 33 '- It is preferred to add suppressions in the code instead of\n' |
| 34 ' sweeping it under the rug here. See:\n' |
| 35 ' http://developer.android.com/tools/debugging/improving-w-lint.html\n' |
| 36 '\n' |
| 37 'Still reading?\n' |
| 38 '- You can edit this file manually to suppress an issue\n' |
| 39 ' globally if it is not applicable to the project.\n' |
| 40 '- You can also rebaseline this file by:\n' |
| 41 ' %s -r\n' |
| 42 ' If the rebaseline is temporary, please make sure there is a bug\n' |
| 43 ' filed and assigned to the author of the offending patch.\n') |
| 44 |
| 45 |
| 46 def _GetClassesDirs(): |
| 47 """Get a list of absolute paths to compiled 'classes' directories.""" |
| 48 classes_dir = [] |
| 49 for dirpath, _, _ in os.walk(constants.GetOutDirectory()): |
| 50 if any([dirpath.startswith(os.path.join(constants.GetOutDirectory(), x)) for |
| 51 x in _EXCLUDE_CLASSES_DIRS]): |
| 52 continue |
| 53 if os.path.basename(dirpath) == 'classes': |
| 54 classes_dir.append(dirpath) |
| 55 |
| 56 assert classes_dir, 'Did not find class files. Did you build?' |
| 57 return classes_dir |
| 58 |
| 59 |
| 60 def _GetSrcDirs(): |
| 61 """Get a list of absolute paths to Java 'src' directories.""" |
| 62 source_dirs = [] |
| 63 for dirpath, _, _ in os.walk(constants.DIR_SOURCE_ROOT): |
| 64 if any([dirpath.startswith(os.path.join(constants.DIR_SOURCE_ROOT, x)) for |
| 65 x in _EXCLUDE_SRC_DIRS]): |
| 66 continue |
| 67 if os.path.basename(dirpath) == 'src' and 'java' in dirpath: |
| 68 source_dirs.append(dirpath) |
| 69 |
| 70 assert source_dirs, 'Did not find any src directories.' |
| 71 return source_dirs |
| 72 |
| 73 |
| 74 def _Lint(project_path, result_xml_path=None, report_html=False, |
| 75 config_xml_path=None, src_dirs=None, classes_dirs=None): |
| 76 """Execute the lint tool.""" |
| 77 |
| 78 cmd = [_LINT_EXE, '-Werror', '--exitcode', '--showall'] |
| 79 |
| 80 if result_xml_path: |
| 81 cmd.extend(['--xml', result_xml_path]) |
| 82 |
| 83 if report_html: |
| 84 _, html_path = tempfile.mkstemp(prefix='lint_', suffix='.html') |
| 85 cmd.extend(['--html', html_path]) |
| 86 |
| 87 if config_xml_path: |
| 88 cmd.extend(['--config', config_xml_path]) |
| 89 |
| 90 if not src_dirs: |
| 91 src_dirs = _GetSrcDirs() |
| 92 for src in src_dirs: |
| 93 cmd.extend(['--sources', os.path.relpath(src, constants.DIR_SOURCE_ROOT)]) |
| 94 |
| 95 if not classes_dirs: |
| 96 classes_dirs = _GetClassesDirs() |
| 97 for cls in classes_dirs: |
| 98 if not os.path.isabs(cls): |
| 99 cls = os.path.join(constants.GetOutDirectory(), cls) |
| 100 cmd.extend(['--classpath', os.path.relpath(cls, constants.DIR_SOURCE_ROOT)]) |
| 101 |
| 102 cmd.append(project_path) |
| 103 return cmd_helper.RunCmd(cmd, cwd=constants.DIR_SOURCE_ROOT) |
| 104 |
| 105 |
| 106 def _Rebaseline(result_xml_path, config_xml_path): |
| 107 """Create a config file which ignores all lint issues. |
| 108 |
| 109 Args: |
| 110 result_xml_path: Result file generated by lint using --xml. |
| 111 config_xml_path: Config file passed to lint using --config. |
| 112 """ |
| 113 dom = minidom.parse(result_xml_path) |
| 114 issues = {} |
| 115 for issue in dom.getElementsByTagName('issue'): |
| 116 issue_id = issue.attributes['id'].value |
| 117 if issue_id not in issues: |
| 118 issues[issue_id] = set() |
| 119 issues[issue_id].add( |
| 120 issue.getElementsByTagName('location')[0].attributes['file'].value) |
| 121 |
| 122 # Get a list of globally ignored issues from existing config file. |
| 123 globally_ignored_issues = [] |
| 124 if os.path.exists(config_xml_path): |
| 125 dom = minidom.parse(config_xml_path) |
| 126 for issue in dom.getElementsByTagName('issue'): |
| 127 if issue.getAttribute('severity') == 'ignore': |
| 128 globally_ignored_issues.append(issue.attributes['id'].value) |
| 129 |
| 130 new_dom = minidom.getDOMImplementation().createDocument(None, 'lint', None) |
| 131 top_element = new_dom.documentElement |
| 132 comment = _DOC % os.path.relpath(config_xml_path, constants.DIR_SOURCE_ROOT) |
| 133 top_element.appendChild(new_dom.createComment(comment)) |
| 134 for issue_id, locations in issues.iteritems(): |
| 135 issue = new_dom.createElement('issue') |
| 136 issue.attributes['id'] = issue_id |
| 137 if issue_id in globally_ignored_issues: |
| 138 print 'Warning: %s is suppresed globally.' % issue_id |
| 139 issue.attributes['severity'] = 'ignore' |
| 140 else: |
| 141 for loc in locations: |
| 142 ignore = new_dom.createElement('ignore') |
| 143 ignore.attributes['path'] = loc |
| 144 issue.appendChild(ignore) |
| 145 top_element.appendChild(issue) |
| 146 |
| 147 with open(config_xml_path, 'w') as f: |
| 148 f.write(new_dom.toprettyxml(indent=' ', encoding='utf-8')) |
| 149 |
| 150 |
| 151 def Run(project_path, config_xml_path, src_dirs, classes_dirs): |
| 152 assert os.path.exists(project_path), 'No such project path: %s' % project_path |
| 153 |
| 154 parser = optparse.OptionParser() |
| 155 parser.add_option('-r', '--rebaseline', |
| 156 action='store_true', |
| 157 help='Rebaseline existing lint issues.') |
| 158 parser.add_option('--release', |
| 159 action='store_true', |
| 160 help='Whether this is a Release build.') |
| 161 parser.add_option('--html', |
| 162 action='store_true', |
| 163 help='Generate a html report instead of writing to stdout.') |
| 164 options, _ = parser.parse_args() |
| 165 |
| 166 if options.release: |
| 167 constants.SetBuildType('Release') |
| 168 else: |
| 169 constants.SetBuildType('Debug') |
| 170 |
| 171 rc = 0 |
| 172 if options.rebaseline: |
| 173 result_xml_path = tempfile.NamedTemporaryFile() |
| 174 _Lint(project_path, result_xml_path=result_xml_path.name, src_dirs=src_dirs, |
| 175 classes_dirs=classes_dirs) |
| 176 _Rebaseline(result_xml_path.name, config_xml_path) |
| 177 print 'Updated %s' % config_xml_path |
| 178 else: |
| 179 rc = _Lint(project_path, report_html=options.html, |
| 180 config_xml_path=config_xml_path, src_dirs=src_dirs, |
| 181 classes_dirs=classes_dirs) |
| 182 if rc: |
| 183 print ('\nWanna suppress errors? Refer to %s' % |
| 184 os.path.relpath(config_xml_path, constants.DIR_SOURCE_ROOT)) |
| 185 |
| 186 return rc |
| 187 |
| 188 |
| 189 def main(argv): |
| 190 project_path = os.path.join(constants.DIR_SOURCE_ROOT, 'chrome', 'android', |
| 191 'testshell', 'java') |
| 192 script_path = os.path.abspath(__file__) |
| 193 config_xml_path = os.path.join(os.path.dirname(script_path), |
| 194 'lint_suppressions.xml') |
| 195 |
| 196 return Run(project_path, config_xml_path, None, None) |
| 197 |
| 198 |
| 199 if __name__ == '__main__': |
| 200 sys.exit(main(sys.argv)) |
OLD | NEW |