diff options
Diffstat (limited to 'third_party/libwebrtc/build/android/gyp/lint.py')
-rwxr-xr-x | third_party/libwebrtc/build/android/gyp/lint.py | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/gyp/lint.py b/third_party/libwebrtc/build/android/gyp/lint.py new file mode 100755 index 0000000000..61763c1624 --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/lint.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Runs Android's lint tool.""" + +from __future__ import print_function + +import argparse +import functools +import logging +import os +import re +import shutil +import sys +import time +import traceback +from xml.dom import minidom +from xml.etree import ElementTree + +from util import build_utils +from util import manifest_utils +from util import server_utils + +_LINT_MD_URL = 'https://chromium.googlesource.com/chromium/src/+/main/build/android/docs/lint.md' # pylint: disable=line-too-long + +# These checks are not useful for chromium. +_DISABLED_ALWAYS = [ + "AppCompatResource", # Lint does not correctly detect our appcompat lib. + "Assert", # R8 --force-enable-assertions is used to enable java asserts. + "InflateParams", # Null is ok when inflating views for dialogs. + "InlinedApi", # Constants are copied so they are always available. + "LintBaseline", # Don't warn about using baseline.xml files. + "MissingApplicationIcon", # False positive for non-production targets. + "SwitchIntDef", # Many C++ enums are not used at all in java. + "UniqueConstants", # Chromium enums allow aliases. + "UnusedAttribute", # Chromium apks have various minSdkVersion values. + "ObsoleteLintCustomCheck", # We have no control over custom lint checks. +] + +# These checks are not useful for test targets and adds an unnecessary burden +# to suppress them. +_DISABLED_FOR_TESTS = [ + # We should not require test strings.xml files to explicitly add + # translatable=false since they are not translated and not used in + # production. + "MissingTranslation", + # Test strings.xml files often have simple names and are not translatable, + # so it may conflict with a production string and cause this error. + "Untranslatable", + # Test targets often use the same strings target and resources target as the + # production targets but may not use all of them. + "UnusedResources", + # TODO(wnwen): Turn this back on since to crash it would require running on + # a device with all the various minSdkVersions. + # Real NewApi violations crash the app, so the only ones that lint catches + # but tests still succeed are false positives. + "NewApi", + # Tests should be allowed to access these methods/classes. + "VisibleForTests", +] + +_RES_ZIP_DIR = 'RESZIPS' +_SRCJAR_DIR = 'SRCJARS' +_AAR_DIR = 'AARS' + + +def _SrcRelative(path): + """Returns relative path to top-level src dir.""" + return os.path.relpath(path, build_utils.DIR_SOURCE_ROOT) + + +def _GenerateProjectFile(android_manifest, + android_sdk_root, + cache_dir, + sources=None, + classpath=None, + srcjar_sources=None, + resource_sources=None, + custom_lint_jars=None, + custom_annotation_zips=None, + android_sdk_version=None): + project = ElementTree.Element('project') + root = ElementTree.SubElement(project, 'root') + # Run lint from output directory: crbug.com/1115594 + root.set('dir', os.getcwd()) + sdk = ElementTree.SubElement(project, 'sdk') + # Lint requires that the sdk path be an absolute path. + sdk.set('dir', os.path.abspath(android_sdk_root)) + cache = ElementTree.SubElement(project, 'cache') + cache.set('dir', cache_dir) + main_module = ElementTree.SubElement(project, 'module') + main_module.set('name', 'main') + main_module.set('android', 'true') + main_module.set('library', 'false') + if android_sdk_version: + main_module.set('compile_sdk_version', android_sdk_version) + manifest = ElementTree.SubElement(main_module, 'manifest') + manifest.set('file', android_manifest) + if srcjar_sources: + for srcjar_file in srcjar_sources: + src = ElementTree.SubElement(main_module, 'src') + src.set('file', srcjar_file) + if sources: + for source in sources: + src = ElementTree.SubElement(main_module, 'src') + src.set('file', source) + if classpath: + for file_path in classpath: + classpath_element = ElementTree.SubElement(main_module, 'classpath') + classpath_element.set('file', file_path) + if resource_sources: + for resource_file in resource_sources: + resource = ElementTree.SubElement(main_module, 'resource') + resource.set('file', resource_file) + if custom_lint_jars: + for lint_jar in custom_lint_jars: + lint = ElementTree.SubElement(main_module, 'lint-checks') + lint.set('file', lint_jar) + if custom_annotation_zips: + for annotation_zip in custom_annotation_zips: + annotation = ElementTree.SubElement(main_module, 'annotations') + annotation.set('file', annotation_zip) + return project + + +def _RetrieveBackportedMethods(backported_methods_path): + with open(backported_methods_path) as f: + methods = f.read().splitlines() + # Methods look like: + # java/util/Set#of(Ljava/lang/Object;)Ljava/util/Set; + # But error message looks like: + # Call requires API level R (current min is 21): java.util.Set#of [NewApi] + methods = (m.replace('/', '\\.') for m in methods) + methods = (m[:m.index('(')] for m in methods) + return sorted(set(methods)) + + +def _GenerateConfigXmlTree(orig_config_path, backported_methods): + if orig_config_path: + root_node = ElementTree.parse(orig_config_path).getroot() + else: + root_node = ElementTree.fromstring('<lint/>') + + issue_node = ElementTree.SubElement(root_node, 'issue') + issue_node.attrib['id'] = 'NewApi' + ignore_node = ElementTree.SubElement(issue_node, 'ignore') + ignore_node.attrib['regexp'] = '|'.join(backported_methods) + return root_node + + +def _GenerateAndroidManifest(original_manifest_path, extra_manifest_paths, + min_sdk_version, android_sdk_version): + # Set minSdkVersion in the manifest to the correct value. + doc, manifest, app_node = manifest_utils.ParseManifest(original_manifest_path) + + # TODO(crbug.com/1126301): Should this be done using manifest merging? + # Add anything in the application node of the extra manifests to the main + # manifest to prevent unused resource errors. + for path in extra_manifest_paths: + _, _, extra_app_node = manifest_utils.ParseManifest(path) + for node in extra_app_node: + app_node.append(node) + + if app_node.find( + '{%s}allowBackup' % manifest_utils.ANDROID_NAMESPACE) is None: + # Assume no backup is intended, appeases AllowBackup lint check and keeping + # it working for manifests that do define android:allowBackup. + app_node.set('{%s}allowBackup' % manifest_utils.ANDROID_NAMESPACE, 'false') + + uses_sdk = manifest.find('./uses-sdk') + if uses_sdk is None: + uses_sdk = ElementTree.Element('uses-sdk') + manifest.insert(0, uses_sdk) + uses_sdk.set('{%s}minSdkVersion' % manifest_utils.ANDROID_NAMESPACE, + min_sdk_version) + uses_sdk.set('{%s}targetSdkVersion' % manifest_utils.ANDROID_NAMESPACE, + android_sdk_version) + return doc + + +def _WriteXmlFile(root, path): + logging.info('Writing xml file %s', path) + build_utils.MakeDirectory(os.path.dirname(path)) + with build_utils.AtomicOutput(path) as f: + # Although we can write it just with ElementTree.tostring, using minidom + # makes it a lot easier to read as a human (also on code search). + f.write( + minidom.parseString(ElementTree.tostring( + root, encoding='utf-8')).toprettyxml(indent=' ').encode('utf-8')) + + +def _RunLint(lint_binary_path, + backported_methods_path, + config_path, + manifest_path, + extra_manifest_paths, + sources, + classpath, + cache_dir, + android_sdk_version, + aars, + srcjars, + min_sdk_version, + resource_sources, + resource_zips, + android_sdk_root, + lint_gen_dir, + baseline, + testonly_target=False, + warnings_as_errors=False): + logging.info('Lint starting') + + cmd = [ + lint_binary_path, + '--quiet', # Silences lint's "." progress updates. + '--disable', + ','.join(_DISABLED_ALWAYS), + ] + + if baseline: + cmd.extend(['--baseline', baseline]) + if testonly_target: + cmd.extend(['--disable', ','.join(_DISABLED_FOR_TESTS)]) + + if not manifest_path: + manifest_path = os.path.join(build_utils.DIR_SOURCE_ROOT, 'build', + 'android', 'AndroidManifest.xml') + + logging.info('Generating config.xml') + backported_methods = _RetrieveBackportedMethods(backported_methods_path) + config_xml_node = _GenerateConfigXmlTree(config_path, backported_methods) + generated_config_path = os.path.join(lint_gen_dir, 'config.xml') + _WriteXmlFile(config_xml_node, generated_config_path) + cmd.extend(['--config', generated_config_path]) + + logging.info('Generating Android manifest file') + android_manifest_tree = _GenerateAndroidManifest(manifest_path, + extra_manifest_paths, + min_sdk_version, + android_sdk_version) + # Include the rebased manifest_path in the lint generated path so that it is + # clear in error messages where the original AndroidManifest.xml came from. + lint_android_manifest_path = os.path.join(lint_gen_dir, manifest_path) + _WriteXmlFile(android_manifest_tree.getroot(), lint_android_manifest_path) + + resource_root_dir = os.path.join(lint_gen_dir, _RES_ZIP_DIR) + # These are zip files with generated resources (e. g. strings from GRD). + logging.info('Extracting resource zips') + for resource_zip in resource_zips: + # Use a consistent root and name rather than a temporary file so that + # suppressions can be local to the lint target and the resource target. + resource_dir = os.path.join(resource_root_dir, resource_zip) + shutil.rmtree(resource_dir, True) + os.makedirs(resource_dir) + resource_sources.extend( + build_utils.ExtractAll(resource_zip, path=resource_dir)) + + logging.info('Extracting aars') + aar_root_dir = os.path.join(lint_gen_dir, _AAR_DIR) + custom_lint_jars = [] + custom_annotation_zips = [] + if aars: + for aar in aars: + # androidx custom lint checks require a newer version of lint. Disable + # until we update see https://crbug.com/1225326 + if 'androidx' in aar: + continue + # Use relative source for aar files since they are not generated. + aar_dir = os.path.join(aar_root_dir, + os.path.splitext(_SrcRelative(aar))[0]) + shutil.rmtree(aar_dir, True) + os.makedirs(aar_dir) + aar_files = build_utils.ExtractAll(aar, path=aar_dir) + for f in aar_files: + if f.endswith('lint.jar'): + custom_lint_jars.append(f) + elif f.endswith('annotations.zip'): + custom_annotation_zips.append(f) + + logging.info('Extracting srcjars') + srcjar_root_dir = os.path.join(lint_gen_dir, _SRCJAR_DIR) + srcjar_sources = [] + if srcjars: + for srcjar in srcjars: + # Use path without extensions since otherwise the file name includes + # .srcjar and lint treats it as a srcjar. + srcjar_dir = os.path.join(srcjar_root_dir, os.path.splitext(srcjar)[0]) + shutil.rmtree(srcjar_dir, True) + os.makedirs(srcjar_dir) + # Sadly lint's srcjar support is broken since it only considers the first + # srcjar. Until we roll a lint version with that fixed, we need to extract + # it ourselves. + srcjar_sources.extend(build_utils.ExtractAll(srcjar, path=srcjar_dir)) + + logging.info('Generating project file') + project_file_root = _GenerateProjectFile(lint_android_manifest_path, + android_sdk_root, cache_dir, sources, + classpath, srcjar_sources, + resource_sources, custom_lint_jars, + custom_annotation_zips, + android_sdk_version) + + project_xml_path = os.path.join(lint_gen_dir, 'project.xml') + _WriteXmlFile(project_file_root, project_xml_path) + cmd += ['--project', project_xml_path] + + logging.info('Preparing environment variables') + env = os.environ.copy() + # It is important that lint uses the checked-in JDK11 as it is almost 50% + # faster than JDK8. + env['JAVA_HOME'] = build_utils.JAVA_HOME + # This is necessary so that lint errors print stack traces in stdout. + env['LINT_PRINT_STACKTRACE'] = 'true' + if baseline and not os.path.exists(baseline): + # Generating new baselines is only done locally, and requires more memory to + # avoid OOMs. + env['LINT_OPTS'] = '-Xmx4g' + else: + # The default set in the wrapper script is 1g, but it seems not enough :( + env['LINT_OPTS'] = '-Xmx2g' + + # This filter is necessary for JDK11. + stderr_filter = build_utils.FilterReflectiveAccessJavaWarnings + stdout_filter = lambda x: build_utils.FilterLines(x, 'No issues found') + + start = time.time() + logging.debug('Lint command %s', ' '.join(cmd)) + failed = True + try: + failed = bool( + build_utils.CheckOutput(cmd, + env=env, + print_stdout=True, + stdout_filter=stdout_filter, + stderr_filter=stderr_filter, + fail_on_output=warnings_as_errors)) + finally: + # When not treating warnings as errors, display the extra footer. + is_debug = os.environ.get('LINT_DEBUG', '0') != '0' + + if failed: + print('- For more help with lint in Chrome:', _LINT_MD_URL) + if is_debug: + print('- DEBUG MODE: Here is the project.xml: {}'.format( + _SrcRelative(project_xml_path))) + else: + print('- Run with LINT_DEBUG=1 to enable lint configuration debugging') + + end = time.time() - start + logging.info('Lint command took %ss', end) + if not is_debug: + shutil.rmtree(aar_root_dir, ignore_errors=True) + shutil.rmtree(resource_root_dir, ignore_errors=True) + shutil.rmtree(srcjar_root_dir, ignore_errors=True) + os.unlink(project_xml_path) + + logging.info('Lint completed') + + +def _ParseArgs(argv): + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument('--target-name', help='Fully qualified GN target name.') + parser.add_argument('--skip-build-server', + action='store_true', + help='Avoid using the build server.') + parser.add_argument('--lint-binary-path', + required=True, + help='Path to lint executable.') + parser.add_argument('--backported-methods', + help='Path to backported methods file created by R8.') + parser.add_argument('--cache-dir', + required=True, + help='Path to the directory in which the android cache ' + 'directory tree should be stored.') + parser.add_argument('--config-path', help='Path to lint suppressions file.') + parser.add_argument('--lint-gen-dir', + required=True, + help='Path to store generated xml files.') + parser.add_argument('--stamp', help='Path to stamp upon success.') + parser.add_argument('--android-sdk-version', + help='Version (API level) of the Android SDK used for ' + 'building.') + parser.add_argument('--min-sdk-version', + required=True, + help='Minimal SDK version to lint against.') + parser.add_argument('--android-sdk-root', + required=True, + help='Lint needs an explicit path to the android sdk.') + parser.add_argument('--testonly', + action='store_true', + help='If set, some checks like UnusedResources will be ' + 'disabled since they are not helpful for test ' + 'targets.') + parser.add_argument('--create-cache', + action='store_true', + help='Whether this invocation is just warming the cache.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + parser.add_argument('--java-sources', + help='File containing a list of java sources files.') + parser.add_argument('--aars', help='GN list of included aars.') + parser.add_argument('--srcjars', help='GN list of included srcjars.') + parser.add_argument('--manifest-path', + help='Path to original AndroidManifest.xml') + parser.add_argument('--extra-manifest-paths', + action='append', + help='GYP-list of manifest paths to merge into the ' + 'original AndroidManifest.xml') + parser.add_argument('--resource-sources', + default=[], + action='append', + help='GYP-list of resource sources files, similar to ' + 'java sources files, but for resource files.') + parser.add_argument('--resource-zips', + default=[], + action='append', + help='GYP-list of resource zips, zip files of generated ' + 'resource files.') + parser.add_argument('--classpath', + help='List of jars to add to the classpath.') + parser.add_argument('--baseline', + help='Baseline file to ignore existing errors and fail ' + 'on new errors.') + + args = parser.parse_args(build_utils.ExpandFileArgs(argv)) + args.java_sources = build_utils.ParseGnList(args.java_sources) + args.aars = build_utils.ParseGnList(args.aars) + args.srcjars = build_utils.ParseGnList(args.srcjars) + args.resource_sources = build_utils.ParseGnList(args.resource_sources) + args.extra_manifest_paths = build_utils.ParseGnList(args.extra_manifest_paths) + args.resource_zips = build_utils.ParseGnList(args.resource_zips) + args.classpath = build_utils.ParseGnList(args.classpath) + return args + + +def main(): + build_utils.InitLogging('LINT_DEBUG') + args = _ParseArgs(sys.argv[1:]) + + # TODO(wnwen): Consider removing lint cache now that there are only two lint + # invocations. + # Avoid parallelizing cache creation since lint runs without the cache defeat + # the purpose of creating the cache in the first place. + if (not args.create_cache and not args.skip_build_server + and server_utils.MaybeRunCommand( + name=args.target_name, argv=sys.argv, stamp_file=args.stamp)): + return + + sources = [] + for java_sources_file in args.java_sources: + sources.extend(build_utils.ReadSourcesList(java_sources_file)) + resource_sources = [] + for resource_sources_file in args.resource_sources: + resource_sources.extend(build_utils.ReadSourcesList(resource_sources_file)) + + possible_depfile_deps = (args.srcjars + args.resource_zips + sources + + resource_sources + [ + args.baseline, + args.manifest_path, + ]) + depfile_deps = [p for p in possible_depfile_deps if p] + + _RunLint(args.lint_binary_path, + args.backported_methods, + args.config_path, + args.manifest_path, + args.extra_manifest_paths, + sources, + args.classpath, + args.cache_dir, + args.android_sdk_version, + args.aars, + args.srcjars, + args.min_sdk_version, + resource_sources, + args.resource_zips, + args.android_sdk_root, + args.lint_gen_dir, + args.baseline, + testonly_target=args.testonly, + warnings_as_errors=args.warnings_as_errors) + logging.info('Creating stamp file') + build_utils.Touch(args.stamp) + + if args.depfile: + build_utils.WriteDepfile(args.depfile, args.stamp, depfile_deps) + + +if __name__ == '__main__': + sys.exit(main()) |