summaryrefslogtreecommitdiff
path: root/scripts/manifest_check.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/manifest_check.py')
-rwxr-xr-xscripts/manifest_check.py256
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: