Reimplement verify_uses_libraries.sh in manifest_check.py.
Previously there were two different scripts that did similar things:
1) build/soong/scripts/manifest_check.py
2) build/make/core/verify_uses_libraries.sh
Both scripts extracted <uses-library> tags and `targetSdkVersion` from
the manifests of Java modules, but 1) worked for XML manifests, and 2)
worked for APKs. This CL reimplements the functionality from 2) in 1),
so that one script can handle both XML manifests and APKs.
Bug: 132357300
Test: lunch cf_x86_64_phone-userdebug && m && launch_cvd \
adb wait-for-device && adb root && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch'
# empty grep output, no errors
Change-Id: Id1b66e4f3f30f307dba70cb111c7571762cb546a
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 0eb1b76..973a675 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -19,6 +19,8 @@
from __future__ import print_function
import argparse
+import re
+import subprocess
import sys
from xml.dom import minidom
@@ -59,64 +61,44 @@
dest='extract_target_sdk_version',
action='store_true',
help='print the targetSdkVersion from the manifest')
+ 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, relax):
- """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 = False):
+ """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
"""
+ if is_apk:
+ manifest_required, manifest_optional = extract_uses_libs_apk(manifest)
+ else:
+ manifest_required, manifest_optional = extract_uses_libs_xml(manifest)
- manifest = parse_manifest(doc)
- elems = get_children_with_tag(manifest, 'application')
- application = elems[0] if len(elems) == 1 else None
- if len(elems) > 1:
- raise RuntimeError('found multiple <application> tags')
- elif not elems:
- if uses_libraries or optional_uses_libraries:
- raise ManifestMismatchError('no <application> tag found')
- return
+ if required is None:
+ required = []
- return verify_uses_library(application, uses_libraries, optional_uses_libraries, relax)
-
-
-def verify_uses_library(application, uses_libraries, optional_uses_libraries, relax):
- """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)
+ if optional is None:
+ optional = []
err = []
- if manifest_uses_libraries != uses_libraries:
+ if manifest_required != required:
err.append('Expected required <uses-library> tags "%s", got "%s"' %
- (', '.join(uses_libraries), ', '.join(manifest_uses_libraries)))
+ (', '.join(required), ', '.join(manifest_required)))
- if manifest_optional_uses_libraries != optional_uses_libraries:
+ if manifest_optional != optional:
err.append('Expected optional <uses-library> tags "%s", got "%s"' %
- (', '.join(optional_uses_libraries), ', '.join(manifest_optional_uses_libraries)))
+ (', '.join(optional), ', '.join(manifest_optional)))
if err:
errmsg = '\n'.join(err)
@@ -126,19 +108,43 @@
return None
-def parse_uses_library(application):
- """Extract uses-library tags from the manifest.
- Args:
- application: the <application> tag in the manifest.
- """
+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 = []
+ for match in re.finditer(pattern, badging):
+ libname = match.group(2)
+ if match.group(1) == None:
+ required.append(libname)
+ else:
+ optional.append(libname)
+
+ return first_unique_elements(required), first_unique_elements(optional)
+
+
+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:
+ raise RuntimeError('found multiple <application> tags')
+ elif not elems:
+ if uses_libraries or optional_uses_libraries:
+ raise ManifestMismatchError('no <application> tag found')
+ return
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)]
- return first_unique_elements(uses_libraries), first_unique_elements(optional_uses_libraries)
+ return first_unique_elements(required), first_unique_elements(optional)
def first_unique_elements(l):
@@ -167,16 +173,34 @@
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)
- manifest = parse_manifest(doc)
+
+def extract_target_sdk_version_apk(badging):
+ """Extract targetSdkVersion tags from the manifest of an APK."""
+
+ pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
+
+ 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')
@@ -203,14 +227,22 @@
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:
# 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(doc, args.uses_libraries,
- args.optional_uses_libraries, args.enforce_uses_libraries_relax)
+ errmsg = enforce_uses_libraries(manifest, args.uses_libraries,
+ args.optional_uses_libraries, args.enforce_uses_libraries_relax,
+ is_apk)
# Create a status file that is empty on success, or contains an error
# message on failure. When exceptions are suppressed, dexpreopt command
@@ -221,11 +253,16 @@
f.write("%s\n" % errmsg)
if args.extract_target_sdk_version:
- print(extract_target_sdk_version(doc))
+ print(extract_target_sdk_version(manifest, is_apk))
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: