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 Args: | |
79 project_path: Absoluate path to the project dir containing the manifest. | |
80 result_xml_path: Result file generated by lint using --xml. | |
81 report_html: Whether to generate a pretty html report instead of outputing | |
82 to stdout. | |
83 config_xml_path: Config file passed to lint using --config. | |
84 src_dirs: List of absolute paths to Java 'src' dirs. | |
85 classes_dirs: List of absolute paths to 'classes' dirs. | |
86 | |
87 Returns: | |
88 Non-zero on failure. | |
89 """ | |
90 | |
91 cmd = [_LINT_EXE, '-Werror', '--exitcode', '--showall'] | |
92 | |
93 if result_xml_path: | |
94 cmd.extend(['--xml', result_xml_path]) | |
95 | |
96 if report_html: | |
97 _, html_path = tempfile.mkstemp(prefix='lint_', suffix='.html') | |
98 cmd.extend(['--html', html_path]) | |
99 | |
100 if config_xml_path: | |
101 cmd.extend(['--config', config_xml_path]) | |
102 | |
103 if not src_dirs: | |
104 src_dirs = _GetSrcDirs() | |
105 for src in src_dirs: | |
106 cmd.extend(['--sources', os.path.relpath(src, constants.DIR_SOURCE_ROOT)]) | |
107 | |
108 if not classes_dirs: | |
109 classes_dirs = _GetClassesDirs() | |
110 for cls in classes_dirs: | |
111 if not os.path.isabs(cls): | |
112 cls = os.path.join(constants.GetOutDirectory(), cls) | |
113 cmd.extend(['--classpath', os.path.relpath(cls, constants.DIR_SOURCE_ROOT)]) | |
114 | |
115 cmd.append(project_path) | |
116 return cmd_helper.RunCmd(cmd, cwd=constants.DIR_SOURCE_ROOT) | |
117 | |
118 | |
119 def _Rebaseline(result_xml_path, config_xml_path, script_path): | |
120 """Create a config file which ignores all lint issues. | |
121 | |
122 Args: | |
123 result_xml_path: Result file generated by lint using --xml. | |
124 config_xml_path: Config file passed to lint using --config. | |
125 script_path: Absolute path to the script being executed. | |
126 """ | |
127 dom = minidom.parse(result_xml_path) | |
128 issues = {} | |
129 for issue in dom.getElementsByTagName('issue'): | |
130 issue_id = issue.attributes['id'].value | |
131 if issue_id not in issues: | |
132 issues[issue_id] = set() | |
133 issues[issue_id].add( | |
134 issue.getElementsByTagName('location')[0].attributes['file'].value) | |
135 | |
136 # Get a list of globally ignored issues from existing config file. | |
137 globally_ignored_issues = [] | |
138 if os.path.exists(config_xml_path): | |
139 dom = minidom.parse(config_xml_path) | |
140 for issue in dom.getElementsByTagName('issue'): | |
141 if issue.getAttribute('severity') == 'ignore': | |
142 globally_ignored_issues.append(issue.attributes['id'].value) | |
143 | |
144 new_dom = minidom.getDOMImplementation().createDocument(None, 'lint', None) | |
145 top_element = new_dom.documentElement | |
146 comment = _DOC % os.path.relpath(script_path, constants.DIR_SOURCE_ROOT) | |
147 top_element.appendChild(new_dom.createComment(comment)) | |
148 for issue_id, locations in issues.iteritems(): | |
149 issue = new_dom.createElement('issue') | |
150 issue.attributes['id'] = issue_id | |
151 if issue_id in globally_ignored_issues: | |
152 print 'Warning: %s is suppresed globally.' % issue_id | |
153 issue.attributes['severity'] = 'ignore' | |
154 else: | |
155 for loc in locations: | |
156 ignore = new_dom.createElement('ignore') | |
157 ignore.attributes['path'] = loc | |
158 issue.appendChild(ignore) | |
159 top_element.appendChild(issue) | |
160 | |
161 with open(config_xml_path, 'w') as f: | |
162 f.write(new_dom.toprettyxml(indent=' ', encoding='utf-8')) | |
163 | |
164 | |
165 def Run(project_path, config_xml_path, script_path, src_dirs, classes_dirs): | |
166 """ | |
167 Args: | |
168 project_path: Absoluate path to the project dir containing the manifest. | |
Yaron
2013/11/15 23:23:47
Absolute
frankf
2013/11/15 23:51:06
Done.
| |
169 config_xml_path: Config file passed to lint using --config. | |
170 script_path: Absolute path to the script being executed. | |
171 src_dirs: List of absolute paths to Java 'src' dirs. | |
172 classes_dirs: List of absolute paths to 'classes' dirs. | |
173 | |
174 Returns: | |
175 Non-zero on failure. | |
176 """ | |
177 assert os.path.exists(project_path), 'No such project path: %s' % project_path | |
178 | |
179 parser = optparse.OptionParser() | |
180 parser.add_option('-r', '--rebaseline', | |
Yaron
2013/11/15 23:23:47
I think it'd be worth making the default project a
| |
181 action='store_true', | |
182 help='Rebaseline existing lint issues.') | |
183 parser.add_option('--release', | |
184 action='store_true', | |
185 help='Whether this is a Release build.') | |
186 parser.add_option('--html', | |
187 action='store_true', | |
188 help='Generate a html report instead of writing to stdout.') | |
189 options, _ = parser.parse_args() | |
190 | |
191 if options.release: | |
192 constants.SetBuildType('Release') | |
193 else: | |
194 constants.SetBuildType('Debug') | |
195 | |
196 rc = 0 | |
197 if options.rebaseline: | |
198 result_xml_path = tempfile.NamedTemporaryFile() | |
199 _Lint(project_path, result_xml_path=result_xml_path.name, src_dirs=src_dirs, | |
200 classes_dirs=classes_dirs) | |
201 _Rebaseline(result_xml_path.name, config_xml_path, script_path) | |
202 print 'Updated %s' % config_xml_path | |
203 else: | |
204 rc = _Lint(project_path, report_html=options.html, | |
205 config_xml_path=config_xml_path, src_dirs=src_dirs, | |
206 classes_dirs=classes_dirs) | |
207 if rc: | |
208 print ('\nWanna suppress errors? Refer to %s' % | |
209 os.path.relpath(config_xml_path, constants.DIR_SOURCE_ROOT)) | |
210 | |
211 return rc | |
212 | |
213 | |
214 def main(argv): | |
215 project_path = os.path.join(constants.DIR_SOURCE_ROOT, 'chrome', 'android', | |
216 'testshell', 'java') | |
217 script_path = os.path.abspath(__file__) | |
218 config_xml_path = os.path.join(os.path.dirname(script_path), | |
219 'lint_suppressions.xml') | |
220 | |
221 return Run(project_path, config_xml_path, script_path, None, None) | |
222 | |
223 | |
224 if __name__ == '__main__': | |
225 sys.exit(main(sys.argv)) | |
OLD | NEW |