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.
7 """Runs Android's lint tool."""
13 from xml
.dom
import minidom
15 from util
import build_utils
18 _SRC_ROOT
= os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
),
22 def _RunLint(lint_path
, config_path
, processed_config_path
, manifest_path
,
23 result_path
, product_dir
, sources
, jar_path
, resource_dir
=None):
25 def _RelativizePath(path
):
26 """Returns relative path to top-level src dir.
29 path: A path relative to cwd.
31 return os
.path
.relpath(os
.path
.abspath(path
), _SRC_ROOT
)
33 def _ProcessConfigFile():
34 if not build_utils
.IsTimeStale(processed_config_path
, [config_path
]):
37 with
open(config_path
, 'rb') as f
:
38 content
= f
.read().replace(
39 'PRODUCT_DIR', _RelativizePath(product_dir
))
41 with
open(processed_config_path
, 'wb') as f
:
44 def _ProcessResultFile():
45 with
open(result_path
, 'rb') as f
:
46 content
= f
.read().replace(
47 _RelativizePath(product_dir
), 'PRODUCT_DIR')
49 with
open(result_path
, 'wb') as f
:
52 def _ParseAndShowResultFile():
53 dom
= minidom
.parse(result_path
)
54 issues
= dom
.getElementsByTagName('issue')
57 issue_id
= issue
.attributes
['id'].value
58 message
= issue
.attributes
['message'].value
59 location_elem
= issue
.getElementsByTagName('location')[0]
60 path
= location_elem
.attributes
['file'].value
61 line
= location_elem
.getAttribute('line')
63 error
= '%s:%s %s: %s [warning]' % (path
, line
, message
, issue_id
)
65 # Issues in class files don't have a line number.
66 error
= '%s %s: %s [warning]' % (path
, message
, issue_id
)
67 print >> sys
.stderr
, error
68 for attr
in ['errorLine1', 'errorLine2']:
69 error_line
= issue
.getAttribute(attr
)
71 print >> sys
.stderr
, error_line
74 with build_utils
.TempDir() as temp_dir
:
78 _RelativizePath(lint_path
), '-Werror', '--exitcode', '--showall',
79 '--config', _RelativizePath(processed_config_path
),
80 '--classpath', _RelativizePath(jar_path
),
81 '--xml', _RelativizePath(result_path
),
84 cmd
.extend(['--resources', _RelativizePath(resource_dir
)])
86 # There may be multiple source files with the same basename (but in
87 # different directories). It is difficult to determine what part of the path
88 # corresponds to the java package, and so instead just link the source files
89 # into temporary directories (creating a new one whenever there is a name
93 new_dir
= os
.path
.join(temp_dir
, str(len(src_dirs
)))
95 src_dirs
.append(new_dir
)
96 cmd
.extend(['--sources', _RelativizePath(new_dir
)])
99 def PathInDir(d
, src
):
100 return os
.path
.join(d
, os
.path
.basename(src
))
105 if not os
.path
.exists(PathInDir(d
, src
)):
109 src_dir
= NewSourceDir()
110 os
.symlink(os
.path
.abspath(src
), PathInDir(src_dir
, src
))
112 cmd
.append(_RelativizePath(os
.path
.join(manifest_path
, os
.pardir
)))
114 if os
.path
.exists(result_path
):
115 os
.remove(result_path
)
118 build_utils
.CheckOutput(cmd
, cwd
=_SRC_ROOT
)
119 except build_utils
.CalledProcessError
as e
:
120 # There is a problem with lint usage
121 if not os
.path
.exists(result_path
):
122 print 'Something is wrong:'
126 # There are actual lint issues
129 num_issues
= _ParseAndShowResultFile()
131 print 'Lint created unparseable xml file...'
132 print 'File contents:'
133 with
open(result_path
) as f
:
138 msg
= ('\nLint found %d new issues.\n'
139 ' - For full explanation refer to %s\n'
140 ' - Wanna suppress these issues?\n'
141 ' 1. Read comment in %s\n'
142 ' 2. Run "python %s %s"\n' %
144 _RelativizePath(result_path
),
145 _RelativizePath(config_path
),
146 _RelativizePath(os
.path
.join(_SRC_ROOT
, 'build', 'android',
147 'lint', 'suppress.py')),
148 _RelativizePath(result_path
)))
149 print >> sys
.stderr
, msg
156 parser
= optparse
.OptionParser()
157 build_utils
.AddDepfileOption(parser
)
158 parser
.add_option('--lint-path', help='Path to lint executable.')
159 parser
.add_option('--config-path', help='Path to lint suppressions file.')
160 parser
.add_option('--processed-config-path',
161 help='Path to processed lint suppressions file.')
162 parser
.add_option('--manifest-path', help='Path to AndroidManifest.xml')
163 parser
.add_option('--result-path', help='Path to XML lint result file.')
164 parser
.add_option('--product-dir', help='Path to product dir.')
165 parser
.add_option('--src-dirs', help='Directories containing java files.')
166 parser
.add_option('--java-files', help='Paths to java files.')
167 parser
.add_option('--jar-path', help='Jar file containing class files.')
168 parser
.add_option('--resource-dir', help='Path to resource dir.')
169 parser
.add_option('--can-fail-build', action
='store_true',
170 help='If set, script will exit with nonzero exit status'
171 ' if lint errors are present')
172 parser
.add_option('--stamp', help='Path to touch on success.')
173 parser
.add_option('--enable', action
='store_true',
174 help='Run lint instead of just touching stamp.')
176 options
, _
= parser
.parse_args()
178 build_utils
.CheckOptions(
179 options
, parser
, required
=['lint_path', 'config_path',
180 'processed_config_path', 'manifest_path',
181 'result_path', 'product_dir',
189 src_dirs
= build_utils
.ParseGypList(options
.src_dirs
)
190 sources
= build_utils
.FindInDirectories(src_dirs
, '*.java')
191 elif options
.java_files
:
192 sources
= build_utils
.ParseGypList(options
.java_files
)
194 print 'One of --src-dirs or --java-files must be specified.'
196 rc
= _RunLint(options
.lint_path
, options
.config_path
,
197 options
.processed_config_path
,
198 options
.manifest_path
, options
.result_path
,
199 options
.product_dir
, sources
, options
.jar_path
,
200 options
.resource_dir
)
203 build_utils
.WriteDepfile(
205 build_utils
.GetPythonDependencies())
207 if options
.stamp
and not rc
:
208 build_utils
.Touch(options
.stamp
)
210 return rc
if options
.can_fail_build
else 0
213 if __name__
== '__main__':