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