Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """Runs Android's lint tool.""" | 7 """Runs Android's lint tool.""" |
| 8 | 8 |
| 9 | 9 |
| 10 import optparse | 10 import argparse |
| 11 import os | 11 import os |
| 12 import sys | 12 import sys |
| 13 import traceback | 13 import traceback |
| 14 from xml.dom import minidom | 14 from xml.dom import minidom |
| 15 | 15 |
| 16 from util import build_utils | 16 from util import build_utils |
| 17 | 17 |
| 18 | 18 |
| 19 _SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), | 19 _SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), |
| 20 '..', '..', '..')) | 20 '..', '..', '..')) |
| 21 | 21 |
| 22 | 22 |
| 23 def _OnStaleMd5(changes, lint_path, config_path, processed_config_path, | 23 def _OnStaleMd5(changes, lint_path, config_path, processed_config_path, |
| 24 manifest_path, result_path, product_dir, sources, jar_path, | 24 manifest_path, result_path, product_dir, sources, jar_path, |
| 25 resource_dir=None, can_fail_build=False): | 25 cache_dir, resource_dir=None, can_fail_build=False, |
| 26 silent=False): | |
| 26 | 27 |
| 27 def _RelativizePath(path): | 28 def _RelativizePath(path): |
| 28 """Returns relative path to top-level src dir. | 29 """Returns relative path to top-level src dir. |
| 29 | 30 |
| 30 Args: | 31 Args: |
| 31 path: A path relative to cwd. | 32 path: A path relative to cwd. |
| 32 """ | 33 """ |
| 33 return os.path.relpath(os.path.abspath(path), _SRC_ROOT) | 34 return os.path.relpath(os.path.abspath(path), _SRC_ROOT) |
| 34 | 35 |
| 35 def _ProcessConfigFile(): | 36 def _ProcessConfigFile(): |
| 37 if not config_path or not processed_config_path: | |
| 38 return | |
| 36 if not build_utils.IsTimeStale(processed_config_path, [config_path]): | 39 if not build_utils.IsTimeStale(processed_config_path, [config_path]): |
| 37 return | 40 return |
| 38 | 41 |
| 39 with open(config_path, 'rb') as f: | 42 with open(config_path, 'rb') as f: |
| 40 content = f.read().replace( | 43 content = f.read().replace( |
| 41 'PRODUCT_DIR', _RelativizePath(product_dir)) | 44 'PRODUCT_DIR', _RelativizePath(product_dir)) |
| 42 | 45 |
| 43 with open(processed_config_path, 'wb') as f: | 46 with open(processed_config_path, 'wb') as f: |
| 44 f.write(content) | 47 f.write(content) |
| 45 | 48 |
| 46 def _ProcessResultFile(): | 49 def _ProcessResultFile(): |
| 47 with open(result_path, 'rb') as f: | 50 with open(result_path, 'rb') as f: |
| 48 content = f.read().replace( | 51 content = f.read().replace( |
| 49 _RelativizePath(product_dir), 'PRODUCT_DIR') | 52 _RelativizePath(product_dir), 'PRODUCT_DIR') |
| 50 | 53 |
| 51 with open(result_path, 'wb') as f: | 54 with open(result_path, 'wb') as f: |
| 52 f.write(content) | 55 f.write(content) |
| 53 | 56 |
| 54 def _ParseAndShowResultFile(): | 57 def _ParseAndShowResultFile(): |
| 55 dom = minidom.parse(result_path) | 58 dom = minidom.parse(result_path) |
| 56 issues = dom.getElementsByTagName('issue') | 59 issues = dom.getElementsByTagName('issue') |
| 57 print >> sys.stderr | 60 if not silent: |
| 58 for issue in issues: | 61 print >> sys.stderr |
| 59 issue_id = issue.attributes['id'].value | 62 for issue in issues: |
| 60 message = issue.attributes['message'].value | 63 issue_id = issue.attributes['id'].value |
| 61 location_elem = issue.getElementsByTagName('location')[0] | 64 message = issue.attributes['message'].value |
| 62 path = location_elem.attributes['file'].value | 65 location_elem = issue.getElementsByTagName('location')[0] |
| 63 line = location_elem.getAttribute('line') | 66 path = location_elem.attributes['file'].value |
| 64 if line: | 67 line = location_elem.getAttribute('line') |
| 65 error = '%s:%s %s: %s [warning]' % (path, line, message, issue_id) | 68 if line: |
| 66 else: | 69 error = '%s:%s %s: %s [warning]' % (path, line, message, issue_id) |
| 67 # Issues in class files don't have a line number. | 70 else: |
| 68 error = '%s %s: %s [warning]' % (path, message, issue_id) | 71 # Issues in class files don't have a line number. |
| 69 print >> sys.stderr, error.encode('utf-8') | 72 error = '%s %s: %s [warning]' % (path, message, issue_id) |
| 70 for attr in ['errorLine1', 'errorLine2']: | 73 print >> sys.stderr, error.encode('utf-8') |
| 71 error_line = issue.getAttribute(attr) | 74 for attr in ['errorLine1', 'errorLine2']: |
| 72 if error_line: | 75 error_line = issue.getAttribute(attr) |
| 73 print >> sys.stderr, error_line.encode('utf-8') | 76 if error_line: |
| 77 print >> sys.stderr, error_line.encode('utf-8') | |
| 74 return len(issues) | 78 return len(issues) |
| 75 | 79 |
| 76 # Need to include all sources when a resource_dir is set so that resources are | 80 # Need to include all sources when a resource_dir is set so that resources are |
| 77 # not marked as unused. | 81 # not marked as unused. |
| 78 if not resource_dir and changes.AddedOrModifiedOnly(): | 82 if not resource_dir and changes.AddedOrModifiedOnly(): |
| 79 changed_paths = set(changes.IterChangedPaths()) | 83 changed_paths = set(changes.IterChangedPaths()) |
| 80 sources = [s for s in sources if s in changed_paths] | 84 sources = [s for s in sources if s in changed_paths] |
| 81 | 85 |
| 82 with build_utils.TempDir() as temp_dir: | 86 with build_utils.TempDir() as temp_dir: |
| 83 _ProcessConfigFile() | 87 _ProcessConfigFile() |
| 84 | 88 |
| 85 cmd = [ | 89 cmd = [ |
| 86 _RelativizePath(lint_path), '-Werror', '--exitcode', '--showall', | 90 _RelativizePath(lint_path), '-Werror', '--exitcode', '--showall', |
| 87 '--config', _RelativizePath(processed_config_path), | |
| 88 '--classpath', _RelativizePath(jar_path), | |
| 89 '--xml', _RelativizePath(result_path), | 91 '--xml', _RelativizePath(result_path), |
| 90 ] | 92 ] |
| 93 if jar_path: | |
| 94 cmd.extend(['--classpath', _RelativizePath(jar_path)]) | |
| 95 if processed_config_path: | |
| 96 cmd.extend(['--config', _RelativizePath(processed_config_path)]) | |
| 91 if resource_dir: | 97 if resource_dir: |
| 92 cmd.extend(['--resources', _RelativizePath(resource_dir)]) | 98 cmd.extend(['--resources', _RelativizePath(resource_dir)]) |
| 93 | 99 |
| 94 # There may be multiple source files with the same basename (but in | 100 # There may be multiple source files with the same basename (but in |
| 95 # different directories). It is difficult to determine what part of the path | 101 # different directories). It is difficult to determine what part of the path |
| 96 # corresponds to the java package, and so instead just link the source files | 102 # corresponds to the java package, and so instead just link the source files |
| 97 # into temporary directories (creating a new one whenever there is a name | 103 # into temporary directories (creating a new one whenever there is a name |
| 98 # conflict). | 104 # conflict). |
| 99 src_dirs = [] | 105 src_dirs = [] |
| 100 def NewSourceDir(): | 106 def NewSourceDir(): |
| 101 new_dir = os.path.join(temp_dir, str(len(src_dirs))) | 107 new_dir = os.path.join(temp_dir, str(len(src_dirs))) |
| 102 os.mkdir(new_dir) | 108 os.mkdir(new_dir) |
| 103 src_dirs.append(new_dir) | 109 src_dirs.append(new_dir) |
| 104 cmd.extend(['--sources', _RelativizePath(new_dir)]) | 110 cmd.extend(['--sources', _RelativizePath(new_dir)]) |
| 105 return new_dir | 111 return new_dir |
| 106 | 112 |
| 107 def PathInDir(d, src): | 113 def PathInDir(d, src): |
| 108 return os.path.join(d, os.path.basename(src)) | 114 return os.path.join(d, os.path.basename(src)) |
| 109 | 115 |
| 110 for src in sources: | 116 for src in sources: |
| 111 src_dir = None | 117 src_dir = None |
| 112 for d in src_dirs: | 118 for d in src_dirs: |
| 113 if not os.path.exists(PathInDir(d, src)): | 119 if not os.path.exists(PathInDir(d, src)): |
| 114 src_dir = d | 120 src_dir = d |
| 115 break | 121 break |
| 116 if not src_dir: | 122 if not src_dir: |
| 117 src_dir = NewSourceDir() | 123 src_dir = NewSourceDir() |
| 118 os.symlink(os.path.abspath(src), PathInDir(src_dir, src)) | 124 os.symlink(os.path.abspath(src), PathInDir(src_dir, src)) |
| 119 | 125 |
| 120 cmd.append(_RelativizePath(os.path.join(manifest_path, os.pardir))) | 126 if manifest_path: |
| 127 cmd.append(_RelativizePath(os.path.join(manifest_path, os.pardir))) | |
| 121 | 128 |
| 122 if os.path.exists(result_path): | 129 if os.path.exists(result_path): |
| 123 os.remove(result_path) | 130 os.remove(result_path) |
| 124 | 131 |
| 132 env = {} | |
| 133 if cache_dir: | |
| 134 env['_JAVA_OPTIONS'] = '-Duser.home=%s' % _RelativizePath(cache_dir) | |
| 135 | |
| 125 try: | 136 try: |
| 126 build_utils.CheckOutput(cmd, cwd=_SRC_ROOT) | 137 build_utils.CheckOutput(cmd, cwd=_SRC_ROOT, env=env or None) |
|
agrieve
2016/03/19 00:12:53
I'm seeing a message "Picked up _JAVA_OPTIONS: ...
jbudorick
2016/03/19 00:57:42
Interesting, I don't see that in either gyp or gn.
agrieve
2016/03/19 01:02:44
Weird - seems to be a common issue:
http://stackov
| |
| 127 except build_utils.CalledProcessError: | 138 except build_utils.CalledProcessError: |
| 128 if can_fail_build: | 139 if can_fail_build: |
| 129 traceback.print_exc() | 140 traceback.print_exc() |
| 130 | 141 |
| 131 # There is a problem with lint usage | 142 # There is a problem with lint usage |
| 132 if not os.path.exists(result_path): | 143 if not os.path.exists(result_path): |
| 133 raise | 144 raise |
| 134 | 145 |
| 135 # There are actual lint issues | 146 # There are actual lint issues |
| 136 else: | 147 else: |
| 137 try: | 148 try: |
| 138 num_issues = _ParseAndShowResultFile() | 149 num_issues = _ParseAndShowResultFile() |
| 139 except Exception: # pylint: disable=broad-except | 150 except Exception: # pylint: disable=broad-except |
| 140 print 'Lint created unparseable xml file...' | 151 if not silent: |
| 141 print 'File contents:' | 152 print 'Lint created unparseable xml file...' |
| 142 with open(result_path) as f: | 153 print 'File contents:' |
| 143 print f.read() | 154 with open(result_path) as f: |
| 155 print f.read() | |
| 144 raise | 156 raise |
| 145 | 157 |
| 146 _ProcessResultFile() | 158 _ProcessResultFile() |
| 147 msg = ('\nLint found %d new issues.\n' | 159 msg = ('\nLint found %d new issues.\n' |
| 148 ' - For full explanation refer to %s\n' | 160 ' - For full explanation refer to %s\n' % |
| 149 ' - Wanna suppress these issues?\n' | |
| 150 ' 1. Read comment in %s\n' | |
| 151 ' 2. Run "python %s %s"\n' % | |
| 152 (num_issues, | 161 (num_issues, |
| 153 _RelativizePath(result_path), | |
| 154 _RelativizePath(config_path), | |
| 155 _RelativizePath(os.path.join(_SRC_ROOT, 'build', 'android', | |
| 156 'lint', 'suppress.py')), | |
| 157 _RelativizePath(result_path))) | 162 _RelativizePath(result_path))) |
| 158 print >> sys.stderr, msg | 163 if config_path: |
| 164 msg += (' - Wanna suppress these issues?\n' | |
| 165 ' 1. Read comment in %s\n' | |
| 166 ' 2. Run "python %s %s"\n' % | |
| 167 (_RelativizePath(config_path), | |
| 168 _RelativizePath(os.path.join(_SRC_ROOT, 'build', 'android', | |
| 169 'lint', 'suppress.py')), | |
| 170 _RelativizePath(result_path))) | |
| 171 if not silent: | |
| 172 print >> sys.stderr, msg | |
| 159 if can_fail_build: | 173 if can_fail_build: |
| 160 raise Exception('Lint failed.') | 174 raise Exception('Lint failed.') |
| 161 | 175 |
| 162 | 176 |
| 163 def main(): | 177 def main(): |
| 164 parser = optparse.OptionParser() | 178 parser = argparse.ArgumentParser() |
| 165 build_utils.AddDepfileOption(parser) | 179 build_utils.AddDepfileOption(parser) |
| 166 parser.add_option('--lint-path', help='Path to lint executable.') | |
| 167 parser.add_option('--config-path', help='Path to lint suppressions file.') | |
| 168 parser.add_option('--processed-config-path', | |
| 169 help='Path to processed lint suppressions file.') | |
| 170 parser.add_option('--manifest-path', help='Path to AndroidManifest.xml') | |
| 171 parser.add_option('--result-path', help='Path to XML lint result file.') | |
| 172 parser.add_option('--product-dir', help='Path to product dir.') | |
| 173 parser.add_option('--src-dirs', help='Directories containing java files.') | |
| 174 parser.add_option('--java-files', help='Paths to java files.') | |
| 175 parser.add_option('--jar-path', help='Jar file containing class files.') | |
| 176 parser.add_option('--resource-dir', help='Path to resource dir.') | |
| 177 parser.add_option('--can-fail-build', action='store_true', | |
| 178 help='If set, script will exit with nonzero exit status' | |
| 179 ' if lint errors are present') | |
| 180 parser.add_option('--stamp', help='Path to touch on success.') | |
| 181 parser.add_option('--enable', action='store_true', | |
| 182 help='Run lint instead of just touching stamp.') | |
| 183 | 180 |
| 184 options, _ = parser.parse_args() | 181 parser.add_argument('--lint-path', required=True, |
| 182 help='Path to lint executable.') | |
| 183 parser.add_argument('--product-dir', required=True, | |
| 184 help='Path to product dir.') | |
| 185 parser.add_argument('--result-path', required=True, | |
| 186 help='Path to XML lint result file.') | |
| 185 | 187 |
| 186 build_utils.CheckOptions( | 188 parser.add_argument('--build-tools-version', |
| 187 options, parser, required=['lint_path', 'config_path', | 189 help='Version of the build tools in the Android SDK.') |
| 188 'processed_config_path', 'manifest_path', | 190 parser.add_argument('--cache-dir', |
| 189 'result_path', 'product_dir', | 191 help='Path to the directory in which the android cache ' |
| 190 'jar_path']) | 192 'directory tree should be stored.') |
| 193 parser.add_argument('--can-fail-build', action='store_true', | |
| 194 help='If set, script will exit with nonzero exit status' | |
| 195 ' if lint errors are present') | |
| 196 parser.add_argument('--config-path', | |
| 197 help='Path to lint suppressions file.') | |
| 198 parser.add_argument('--enable', action='store_true', | |
| 199 help='Run lint instead of just touching stamp.') | |
| 200 parser.add_argument('--jar-path', | |
| 201 help='Jar file containing class files.') | |
| 202 parser.add_argument('--java-files', | |
| 203 help='Paths to java files.') | |
| 204 parser.add_argument('--manifest-path', | |
| 205 help='Path to AndroidManifest.xml') | |
| 206 parser.add_argument('--platform-xml-path', | |
| 207 help='Path to api-platforms.xml') | |
| 208 parser.add_argument('--processed-config-path', | |
| 209 help='Path to processed lint suppressions file.') | |
| 210 parser.add_argument('--resource-dir', | |
| 211 help='Path to resource dir.') | |
| 212 parser.add_argument('--silent', action='store_true', | |
| 213 help='If set, script will not log anything.') | |
| 214 parser.add_argument('--src-dirs', | |
| 215 help='Directories containing java files.') | |
| 216 parser.add_argument('--stamp', | |
| 217 help='Path to touch on success.') | |
| 191 | 218 |
| 192 if options.enable: | 219 args = parser.parse_args() |
| 220 | |
| 221 if args.enable: | |
| 193 sources = [] | 222 sources = [] |
| 194 if options.src_dirs: | 223 if args.src_dirs: |
| 195 src_dirs = build_utils.ParseGypList(options.src_dirs) | 224 src_dirs = build_utils.ParseGypList(args.src_dirs) |
| 196 sources = build_utils.FindInDirectories(src_dirs, '*.java') | 225 sources = build_utils.FindInDirectories(src_dirs, '*.java') |
| 197 elif options.java_files: | 226 elif args.java_files: |
| 198 sources = build_utils.ParseGypList(options.java_files) | 227 sources = build_utils.ParseGypList(args.java_files) |
| 199 else: | 228 |
| 200 print 'One of --src-dirs or --java-files must be specified.' | 229 if args.config_path and not args.processed_config_path: |
| 201 return 1 | 230 parser.error('--config-path specified without --processed-config-path') |
| 231 elif args.processed_config_path and not args.config_path: | |
| 232 parser.error('--processed-config-path specified without --config-path') | |
| 202 | 233 |
| 203 input_paths = [ | 234 input_paths = [ |
| 204 options.lint_path, | 235 args.lint_path, |
| 205 options.config_path, | |
| 206 options.manifest_path, | |
| 207 options.jar_path, | |
| 208 ] | 236 ] |
| 209 input_paths.extend(sources) | 237 if args.config_path: |
| 210 if options.resource_dir: | 238 input_paths.append(args.config_path) |
| 211 input_paths.extend(build_utils.FindInDirectory(options.resource_dir, '*')) | 239 if args.jar_path: |
| 240 input_paths.append(args.jar_path) | |
| 241 if args.manifest_path: | |
| 242 input_paths.append(args.manifest_path) | |
| 243 if args.platform_xml_path: | |
| 244 input_paths.append(args.platform_xml_path) | |
| 245 if args.resource_dir: | |
| 246 input_paths.extend(build_utils.FindInDirectory(args.resource_dir, '*')) | |
| 247 if sources: | |
| 248 input_paths.extend(sources) | |
| 212 | 249 |
| 213 input_strings = [ options.processed_config_path ] | 250 input_strings = [] |
| 214 output_paths = [ options.result_path ] | 251 if args.processed_config_path: |
| 252 input_strings.append(args.processed_config_path) | |
| 253 | |
| 254 output_paths = [ args.result_path ] | |
| 255 if args.cache_dir: | |
| 256 if not args.build_tools_version: | |
| 257 parser.error('--cache-dir specified without --build-tools-version') | |
| 258 output_paths.append(os.path.join( | |
| 259 args.cache_dir, '.android', 'cache', | |
| 260 'api-versions-6-%s.bin' % args.build_tools_version)) | |
| 215 | 261 |
| 216 build_utils.CallAndWriteDepfileIfStale( | 262 build_utils.CallAndWriteDepfileIfStale( |
| 217 lambda changes: _OnStaleMd5(changes, options.lint_path, | 263 lambda changes: _OnStaleMd5(changes, args.lint_path, |
| 218 options.config_path, | 264 args.config_path, |
| 219 options.processed_config_path, | 265 args.processed_config_path, |
| 220 options.manifest_path, options.result_path, | 266 args.manifest_path, args.result_path, |
| 221 options.product_dir, sources, | 267 args.product_dir, sources, |
| 222 options.jar_path, | 268 args.jar_path, |
| 223 resource_dir=options.resource_dir, | 269 args.cache_dir, |
| 224 can_fail_build=options.can_fail_build), | 270 resource_dir=args.resource_dir, |
| 225 options, | 271 can_fail_build=args.can_fail_build, |
| 272 silent=args.silent), | |
| 273 args, | |
| 226 input_paths=input_paths, | 274 input_paths=input_paths, |
| 227 input_strings=input_strings, | 275 input_strings=input_strings, |
| 228 output_paths=output_paths, | 276 output_paths=output_paths, |
| 229 pass_changes=True) | 277 pass_changes=True) |
| 230 | 278 |
| 231 | 279 |
| 232 if __name__ == '__main__': | 280 if __name__ == '__main__': |
| 233 sys.exit(main()) | 281 sys.exit(main()) |
| OLD | NEW |