diff options
Diffstat (limited to 'scripts/manifest_check.py')
-rwxr-xr-x | scripts/manifest_check.py | 256 |
1 files changed, 189 insertions, 67 deletions
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py index 9122da1fb..8168fbf6a 100755 --- a/scripts/manifest_check.py +++ b/scripts/manifest_check.py @@ -19,6 +19,9 @@ from __future__ import print_function import argparse +import json +import re +import subprocess import sys from xml.dom import minidom @@ -48,29 +51,99 @@ def parse_args(): dest='enforce_uses_libraries', action='store_true', help='check the uses-library entries known to the build system against the manifest') + parser.add_argument('--enforce-uses-libraries-relax', + dest='enforce_uses_libraries_relax', + action='store_true', + help='do not fail immediately, just save the error message to file') + parser.add_argument('--enforce-uses-libraries-status', + dest='enforce_uses_libraries_status', + help='output file to store check status (error message)') parser.add_argument('--extract-target-sdk-version', dest='extract_target_sdk_version', action='store_true', help='print the targetSdkVersion from the manifest') + parser.add_argument('--dexpreopt-config', + dest='dexpreopt_configs', + action='append', + help='a paths to a dexpreopt.config of some library') + parser.add_argument('--aapt', + dest='aapt', + help='path to aapt executable') parser.add_argument('--output', '-o', dest='output', help='output AndroidManifest.xml file') parser.add_argument('input', help='input AndroidManifest.xml file') return parser.parse_args() -def enforce_uses_libraries(doc, uses_libraries, optional_uses_libraries): - """Verify that the <uses-library> tags in the manifest match those provided by the build system. +def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path): + """Verify that the <uses-library> tags in the manifest match those provided + by the build system. Args: - doc: The XML document. - uses_libraries: The names of <uses-library> tags known to the build system - optional_uses_libraries: The names of <uses-library> tags with required:fals - known to the build system - Raises: - RuntimeError: Invalid manifest - ManifestMismatchError: Manifest does not match + manifest: manifest (either parsed XML or aapt dump of APK) + required: required libs known to the build system + optional: optional libs known to the build system + relax: if true, suppress error on mismatch and just write it to file + is_apk: if the manifest comes from an APK or an XML file """ - - manifest = parse_manifest(doc) + if is_apk: + manifest_required, manifest_optional, tags = extract_uses_libs_apk(manifest) + else: + manifest_required, manifest_optional, tags = extract_uses_libs_xml(manifest) + + if manifest_required == required and manifest_optional == optional: + return None + + errmsg = ''.join([ + 'mismatch in the <uses-library> tags between the build system and the ' + 'manifest:\n', + '\t- required libraries in build system: [%s]\n' % ', '.join(required), + '\t vs. in the manifest: [%s]\n' % ', '.join(manifest_required), + '\t- optional libraries in build system: [%s]\n' % ', '.join(optional), + '\t vs. in the manifest: [%s]\n' % ', '.join(manifest_optional), + '\t- tags in the manifest (%s):\n' % path, + '\t\t%s\n' % '\t\t'.join(tags), + 'note: the following options are available:\n', + '\t- to temporarily disable the check on command line, rebuild with ', + 'RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" ', + 'and disable AOT-compilation in dexpreopt)\n', + '\t- to temporarily disable the check for the whole product, set ', + 'PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles\n', + '\t- to fix the check, make build system properties coherent with the ' + 'manifest\n', + '\t- see build/make/Changes.md for details\n']) + + if not relax: + raise ManifestMismatchError(errmsg) + + return errmsg + + +def extract_uses_libs_apk(badging): + """Extract <uses-library> tags from the manifest of an APK.""" + + pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE) + + required = [] + optional = [] + lines = [] + for match in re.finditer(pattern, badging): + lines.append(match.group(0)) + libname = match.group(2) + if match.group(1) == None: + required.append(libname) + else: + optional.append(libname) + + required = first_unique_elements(required) + optional = first_unique_elements(optional) + tags = first_unique_elements(lines) + return required, optional, tags + + +def extract_uses_libs_xml(xml): + """Extract <uses-library> tags from the manifest.""" + + manifest = parse_manifest(xml) elems = get_children_with_tag(manifest, 'application') application = elems[0] if len(elems) == 1 else None if len(elems) > 1: @@ -80,54 +153,20 @@ def enforce_uses_libraries(doc, uses_libraries, optional_uses_libraries): raise ManifestMismatchError('no <application> tag found') return - verify_uses_library(application, uses_libraries, optional_uses_libraries) - - -def verify_uses_library(application, uses_libraries, optional_uses_libraries): - """Verify that the uses-library values known to the build system match the manifest. - - Args: - application: the <application> tag in the manifest. - uses_libraries: the names of expected <uses-library> tags. - optional_uses_libraries: the names of expected <uses-library> tags with required="false". - Raises: - ManifestMismatchError: Manifest does not match - """ - - if uses_libraries is None: - uses_libraries = [] - - if optional_uses_libraries is None: - optional_uses_libraries = [] - - manifest_uses_libraries, manifest_optional_uses_libraries = parse_uses_library(application) - - err = [] - if manifest_uses_libraries != uses_libraries: - err.append('Expected required <uses-library> tags "%s", got "%s"' % - (', '.join(uses_libraries), ', '.join(manifest_uses_libraries))) - - if manifest_optional_uses_libraries != optional_uses_libraries: - err.append('Expected optional <uses-library> tags "%s", got "%s"' % - (', '.join(optional_uses_libraries), ', '.join(manifest_optional_uses_libraries))) - - if err: - raise ManifestMismatchError('\n'.join(err)) - - -def parse_uses_library(application): - """Extract uses-library tags from the manifest. - - Args: - application: the <application> tag in the manifest. - """ - libs = get_children_with_tag(application, 'uses-library') - uses_libraries = [uses_library_name(x) for x in libs if uses_library_required(x)] - optional_uses_libraries = [uses_library_name(x) for x in libs if not uses_library_required(x)] + required = [uses_library_name(x) for x in libs if uses_library_required(x)] + optional = [uses_library_name(x) for x in libs if not uses_library_required(x)] + + # render <uses-library> tags as XML for a pretty error message + tags = [] + for lib in libs: + tags.append(lib.toprettyxml()) - return first_unique_elements(uses_libraries), first_unique_elements(optional_uses_libraries) + required = first_unique_elements(required) + optional = first_unique_elements(optional) + tags = first_unique_elements(tags) + return required, optional, tags def first_unique_elements(l): @@ -156,16 +195,34 @@ def uses_library_required(lib): return (required.value == 'true') if required is not None else True -def extract_target_sdk_version(doc): +def extract_target_sdk_version(manifest, is_apk = False): """Returns the targetSdkVersion from the manifest. Args: - doc: The XML document. - Raises: - RuntimeError: invalid manifest + manifest: manifest (either parsed XML or aapt dump of APK) + is_apk: if the manifest comes from an APK or an XML file """ + if is_apk: + return extract_target_sdk_version_apk(manifest) + else: + return extract_target_sdk_version_xml(manifest) + + +def extract_target_sdk_version_apk(badging): + """Extract targetSdkVersion tags from the manifest of an APK.""" + + pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE) - manifest = parse_manifest(doc) + for match in re.finditer(pattern, badging): + return match.group(1) + + raise RuntimeError('cannot find targetSdkVersion in the manifest') + + +def extract_target_sdk_version_xml(xml): + """Extract targetSdkVersion tags from the manifest.""" + + manifest = parse_manifest(xml) # Get or insert the uses-sdk element uses_sdk = get_children_with_tag(manifest, 'uses-sdk') @@ -187,24 +244,89 @@ def extract_target_sdk_version(doc): return target_attr.value +def load_dexpreopt_configs(configs): + """Load dexpreopt.config files and map module names to library names.""" + module_to_libname = {} + + if configs is None: + configs = [] + + for config in configs: + with open(config, 'r') as f: + contents = json.load(f) + module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary'] + + return module_to_libname + + +def translate_libnames(modules, module_to_libname): + """Translate module names into library names using the mapping.""" + if modules is None: + modules = [] + + libnames = [] + for name in modules: + if name in module_to_libname: + name = module_to_libname[name] + libnames.append(name) + + return libnames + + def main(): """Program entry point.""" try: args = parse_args() - doc = minidom.parse(args.input) + # The input can be either an XML manifest or an APK, they are parsed and + # processed in different ways. + is_apk = args.input.endswith('.apk') + if is_apk: + aapt = args.aapt if args.aapt != None else "aapt" + manifest = subprocess.check_output([aapt, "dump", "badging", args.input]) + else: + manifest = minidom.parse(args.input) if args.enforce_uses_libraries: - enforce_uses_libraries(doc, - args.uses_libraries, - args.optional_uses_libraries) + # Load dexpreopt.config files and build a mapping from module names to + # library names. This is necessary because build system addresses + # libraries by their module name (`uses_libs`, `optional_uses_libs`, + # `LOCAL_USES_LIBRARIES`, `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain + # module names), while the manifest addresses libraries by their name. + mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs) + required = translate_libnames(args.uses_libraries, mod_to_lib) + optional = translate_libnames(args.optional_uses_libraries, mod_to_lib) + + # Check if the <uses-library> lists in the build system agree with those + # in the manifest. Raise an exception on mismatch, unless the script was + # passed a special parameter to suppress exceptions. + errmsg = enforce_uses_libraries(manifest, required, optional, + args.enforce_uses_libraries_relax, is_apk, args.input) + + # Create a status file that is empty on success, or contains an error + # message on failure. When exceptions are suppressed, dexpreopt command + # command will check file size to determine if the check has failed. + if args.enforce_uses_libraries_status: + with open(args.enforce_uses_libraries_status, 'w') as f: + if not errmsg == None: + f.write("%s\n" % errmsg) if args.extract_target_sdk_version: - print(extract_target_sdk_version(doc)) + try: + print(extract_target_sdk_version(manifest, is_apk)) + except: + # Failed; don't crash, return "any" SDK version. This will result in + # dexpreopt not adding any compatibility libraries. + print(10000) if args.output: + # XML output is supposed to be written only when this script is invoked + # with XML input manifest, not with an APK. + if is_apk: + raise RuntimeError('cannot save APK manifest as XML') + with open(args.output, 'wb') as f: - write_xml(f, doc) + write_xml(f, manifest) # pylint: disable=broad-except except Exception as err: |