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
):
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 # There may be multiple source files with the same basename (but in
85 # different directories). It is difficult to determine what part of the path
86 # corresponds to the java package, and so instead just link the source files
87 # into temporary directories (creating a new one whenever there is a name
91 new_dir
= os
.path
.join(temp_dir
, str(len(src_dirs
)))
93 src_dirs
.append(new_dir
)
94 cmd
.extend(['--sources', _RelativizePath(new_dir
)])
97 def PathInDir(d
, src
):
98 return os
.path
.join(d
, os
.path
.basename(src
))
103 if not os
.path
.exists(PathInDir(d
, src
)):
107 src_dir
= NewSourceDir()
108 os
.symlink(os
.path
.abspath(src
), PathInDir(src_dir
, src
))
110 cmd
.append(_RelativizePath(os
.path
.join(manifest_path
, os
.pardir
)))
112 if os
.path
.exists(result_path
):
113 os
.remove(result_path
)
116 build_utils
.CheckOutput(cmd
, cwd
=_SRC_ROOT
)
117 except build_utils
.CalledProcessError
as e
:
118 # There is a problem with lint usage
119 if not os
.path
.exists(result_path
):
120 print 'Something is wrong:'
124 # There are actual lint issues
127 num_issues
= _ParseAndShowResultFile()
129 print 'Lint created unparseable xml file...'
130 print 'File contents:'
131 with
open(result_path
) as f
:
136 msg
= ('\nLint found %d new issues.\n'
137 ' - For full explanation refer to %s\n'
138 ' - Wanna suppress these issues?\n'
139 ' 1. Read comment in %s\n'
140 ' 2. Run "python %s %s"\n' %
142 _RelativizePath(result_path
),
143 _RelativizePath(config_path
),
144 _RelativizePath(os
.path
.join(_SRC_ROOT
, 'build', 'android',
145 'lint', 'suppress.py')),
146 _RelativizePath(result_path
)))
147 print >> sys
.stderr
, msg
148 # Lint errors do not fail the build.
155 parser
= optparse
.OptionParser()
156 build_utils
.AddDepfileOption(parser
)
157 parser
.add_option('--lint-path', help='Path to lint executable.')
158 parser
.add_option('--config-path', help='Path to lint suppressions file.')
159 parser
.add_option('--processed-config-path',
160 help='Path to processed lint suppressions file.')
161 parser
.add_option('--manifest-path', help='Path to AndroidManifest.xml')
162 parser
.add_option('--result-path', help='Path to XML lint result file.')
163 parser
.add_option('--product-dir', help='Path to product dir.')
164 parser
.add_option('--src-dirs', help='Directories containing java files.')
165 parser
.add_option('--java-files', help='Paths to java files.')
166 parser
.add_option('--jar-path', help='Jar file containing class files.')
167 parser
.add_option('--stamp', help='Path to touch on success.')
168 parser
.add_option('--enable', action
='store_true',
169 help='Run lint instead of just touching stamp.')
171 options
, _
= parser
.parse_args()
173 build_utils
.CheckOptions(
174 options
, parser
, required
=['lint_path', 'config_path',
175 'processed_config_path', 'manifest_path',
176 'result_path', 'product_dir',
184 src_dirs
= build_utils
.ParseGypList(options
.src_dirs
)
185 sources
= build_utils
.FindInDirectories(src_dirs
, '*.java')
186 elif options
.java_files
:
187 sources
= build_utils
.ParseGypList(options
.java_files
)
189 print 'One of --src-dirs or --java-files must be specified.'
191 rc
= _RunLint(options
.lint_path
, options
.config_path
,
192 options
.processed_config_path
,
193 options
.manifest_path
, options
.result_path
,
194 options
.product_dir
, sources
, options
.jar_path
)
197 build_utils
.WriteDepfile(
199 build_utils
.GetPythonDependencies())
201 if options
.stamp
and not rc
:
202 build_utils
.Touch(options
.stamp
)
207 if __name__
== '__main__':