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,
24 can_fail_build
=False):
26 def _RelativizePath(path
):
27 """Returns relative path to top-level src dir.
30 path: A path relative to cwd.
32 return os
.path
.relpath(os
.path
.abspath(path
), _SRC_ROOT
)
34 def _ProcessConfigFile():
35 if not build_utils
.IsTimeStale(processed_config_path
, [config_path
]):
38 with
open(config_path
, 'rb') as f
:
39 content
= f
.read().replace(
40 'PRODUCT_DIR', _RelativizePath(product_dir
))
42 with
open(processed_config_path
, 'wb') as f
:
45 def _ProcessResultFile():
46 with
open(result_path
, 'rb') as f
:
47 content
= f
.read().replace(
48 _RelativizePath(product_dir
), 'PRODUCT_DIR')
50 with
open(result_path
, 'wb') as f
:
53 def _ParseAndShowResultFile():
54 dom
= minidom
.parse(result_path
)
55 issues
= dom
.getElementsByTagName('issue')
58 issue_id
= issue
.attributes
['id'].value
59 message
= issue
.attributes
['message'].value
60 location_elem
= issue
.getElementsByTagName('location')[0]
61 path
= location_elem
.attributes
['file'].value
62 line
= location_elem
.getAttribute('line')
64 error
= '%s:%s %s: %s [warning]' % (path
, line
, message
, issue_id
)
66 # Issues in class files don't have a line number.
67 error
= '%s %s: %s [warning]' % (path
, message
, issue_id
)
68 print >> sys
.stderr
, error
69 for attr
in ['errorLine1', 'errorLine2']:
70 error_line
= issue
.getAttribute(attr
)
72 print >> sys
.stderr
, error_line
75 with build_utils
.TempDir() as temp_dir
:
79 _RelativizePath(lint_path
), '-Werror', '--exitcode', '--showall',
80 '--config', _RelativizePath(processed_config_path
),
81 '--classpath', _RelativizePath(jar_path
),
82 '--xml', _RelativizePath(result_path
),
85 cmd
.extend(['--resources', _RelativizePath(resource_dir
)])
87 # There may be multiple source files with the same basename (but in
88 # different directories). It is difficult to determine what part of the path
89 # corresponds to the java package, and so instead just link the source files
90 # into temporary directories (creating a new one whenever there is a name
94 new_dir
= os
.path
.join(temp_dir
, str(len(src_dirs
)))
96 src_dirs
.append(new_dir
)
97 cmd
.extend(['--sources', _RelativizePath(new_dir
)])
100 def PathInDir(d
, src
):
101 return os
.path
.join(d
, os
.path
.basename(src
))
106 if not os
.path
.exists(PathInDir(d
, src
)):
110 src_dir
= NewSourceDir()
111 os
.symlink(os
.path
.abspath(src
), PathInDir(src_dir
, src
))
113 cmd
.append(_RelativizePath(os
.path
.join(manifest_path
, os
.pardir
)))
115 if os
.path
.exists(result_path
):
116 os
.remove(result_path
)
119 build_utils
.CheckOutput(cmd
, cwd
=_SRC_ROOT
)
120 except build_utils
.CalledProcessError
as e
:
121 # There is a problem with lint usage
122 if not os
.path
.exists(result_path
):
123 print 'Something is wrong:'
127 # There are actual lint issues
130 num_issues
= _ParseAndShowResultFile()
132 print 'Lint created unparseable xml file...'
133 print 'File contents:'
134 with
open(result_path
) as f
:
139 msg
= ('\nLint found %d new issues.\n'
140 ' - For full explanation refer to %s\n'
141 ' - Wanna suppress these issues?\n'
142 ' 1. Read comment in %s\n'
143 ' 2. Run "python %s %s"\n' %
145 _RelativizePath(result_path
),
146 _RelativizePath(config_path
),
147 _RelativizePath(os
.path
.join(_SRC_ROOT
, 'build', 'android',
148 'lint', 'suppress.py')),
149 _RelativizePath(result_path
)))
150 print >> sys
.stderr
, msg
151 return 1 if can_fail_build
else 0
157 parser
= optparse
.OptionParser()
158 build_utils
.AddDepfileOption(parser
)
159 parser
.add_option('--lint-path', help='Path to lint executable.')
160 parser
.add_option('--config-path', help='Path to lint suppressions file.')
161 parser
.add_option('--processed-config-path',
162 help='Path to processed lint suppressions file.')
163 parser
.add_option('--manifest-path', help='Path to AndroidManifest.xml')
164 parser
.add_option('--result-path', help='Path to XML lint result file.')
165 parser
.add_option('--product-dir', help='Path to product dir.')
166 parser
.add_option('--src-dirs', help='Directories containing java files.')
167 parser
.add_option('--java-files', help='Paths to java files.')
168 parser
.add_option('--jar-path', help='Jar file containing class files.')
169 parser
.add_option('--resource-dir', help='Path to resource dir.')
170 parser
.add_option('--can-fail-build', action
='store_true',
171 help='If set, script will exit with nonzero exit status'
172 ' if lint errors are present')
173 parser
.add_option('--stamp', help='Path to touch on success.')
174 parser
.add_option('--enable', action
='store_true',
175 help='Run lint instead of just touching stamp.')
177 options
, _
= parser
.parse_args()
179 build_utils
.CheckOptions(
180 options
, parser
, required
=['lint_path', 'config_path',
181 'processed_config_path', 'manifest_path',
182 'result_path', 'product_dir',
190 src_dirs
= build_utils
.ParseGypList(options
.src_dirs
)
191 sources
= build_utils
.FindInDirectories(src_dirs
, '*.java')
192 elif options
.java_files
:
193 sources
= build_utils
.ParseGypList(options
.java_files
)
195 print 'One of --src-dirs or --java-files must be specified.'
197 rc
= _RunLint(options
.lint_path
, options
.config_path
,
198 options
.processed_config_path
,
199 options
.manifest_path
, options
.result_path
,
200 options
.product_dir
, sources
, options
.jar_path
,
201 options
.resource_dir
, options
.can_fail_build
)
204 build_utils
.WriteDepfile(
206 build_utils
.GetPythonDependencies())
208 if options
.stamp
and not rc
:
209 build_utils
.Touch(options
.stamp
)
214 if __name__
== '__main__':