diff options
author | 2021-08-25 20:01:17 +0000 | |
---|---|---|
committer | 2021-09-08 03:59:40 +0000 | |
commit | f880742582a2876080427fcebb1b2c94787c4e24 (patch) | |
tree | 8f287bdcbe1a7d72cac95d9062ff4108daefebfe /scripts/manifest_check.py | |
parent | 7c16dabfa595cfe38b99c5b0f92aca6eec440de0 (diff) |
Apply pylint to scripts/manifest_check*.py
1. Run pyformat scripts/<script>.py -s 4 --force_quote_type
none -i to fix formatting
2. Annotate #pylint: disable, where straightforward fix is not available
Test: m manifest_check_test
Test: pylint --rcfile tools/repohooks/tools/pylintrc
build/soong/scripts/manifest_check.py
build/soong/scripts/manifest_check_test.py
Bug: 195738175
Change-Id: I9af498c4abd6ac9f8b9df4f93cbdd4424eacff8e
Diffstat (limited to 'scripts/manifest_check.py')
-rwxr-xr-x | scripts/manifest_check.py | 529 |
1 files changed, 280 insertions, 249 deletions
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py index 4ef4399ca..71fe358ff 100755 --- a/scripts/manifest_check.py +++ b/scripts/manifest_check.py @@ -25,7 +25,6 @@ import subprocess import sys from xml.dom import minidom - from manifest import android_ns from manifest import get_children_with_tag from manifest import parse_manifest @@ -33,49 +32,61 @@ from manifest import write_xml class ManifestMismatchError(Exception): - pass + pass def parse_args(): - """Parse commandline arguments.""" - - parser = argparse.ArgumentParser() - parser.add_argument('--uses-library', dest='uses_libraries', - action='append', - help='specify uses-library entries known to the build system') - parser.add_argument('--optional-uses-library', - dest='optional_uses_libraries', - action='append', - help='specify uses-library entries known to the build system with required:false') - parser.add_argument('--enforce-uses-libraries', - 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() + """Parse commandline arguments.""" + + parser = argparse.ArgumentParser() + parser.add_argument( + '--uses-library', + dest='uses_libraries', + action='append', + help='specify uses-library entries known to the build system') + parser.add_argument( + '--optional-uses-library', + dest='optional_uses_libraries', + action='append', + help='specify uses-library entries known to the build system with ' + 'required:false' + ) + parser.add_argument( + '--enforce-uses-libraries', + 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(manifest, required, optional, relax, is_apk, path): - """Verify that the <uses-library> tags in the manifest match those provided + """Verify that the <uses-library> tags in the manifest match those provided + by the build system. Args: @@ -84,274 +95,294 @@ def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path): 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, tags = extract_uses_libs_apk(manifest) - else: - manifest_required, manifest_optional, tags = extract_uses_libs_xml(manifest) - - # Trim namespace component. Normally Soong does that automatically when it - # handles module names specified in Android.bp properties. However not all - # <uses-library> entries in the manifest correspond to real modules: some of - # the optional libraries may be missing at build time. Therefor this script - # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the - # optional namespace part manually. - required = trim_namespace_parts(required) - optional = trim_namespace_parts(optional) - - 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 - - -MODULE_NAMESPACE = re.compile("^//[^:]+:") - -def trim_namespace_parts(modules): - """Trim the namespace part of each module, if present. Leave only the name.""" - - trimmed = [] - for module in modules: - trimmed.append(MODULE_NAMESPACE.sub('', module)) - return trimmed - - -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) + """ + if is_apk: + manifest_required, manifest_optional, tags = extract_uses_libs_apk( + manifest) else: - optional.append(libname) - - required = first_unique_elements(required) - optional = first_unique_elements(optional) - tags = first_unique_elements(lines) - return required, optional, tags + manifest_required, manifest_optional, tags = extract_uses_libs_xml( + manifest) + + # Trim namespace component. Normally Soong does that automatically when it + # handles module names specified in Android.bp properties. However not all + # <uses-library> entries in the manifest correspond to real modules: some of + # the optional libraries may be missing at build time. Therefor this script + # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the + # optional namespace part manually. + required = trim_namespace_parts(required) + optional = trim_namespace_parts(optional) + + if manifest_required == required and manifest_optional == optional: + return None + + #pylint: disable=line-too-long + 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' + ]) + #pylint: enable=line-too-long + + if not relax: + raise ManifestMismatchError(errmsg) + + return errmsg + + +MODULE_NAMESPACE = re.compile('^//[^:]+:') -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 +def trim_namespace_parts(modules): + """Trim the namespace part of each module, if present. - libs = get_children_with_tag(application, 'uses-library') + Leave only the name. + """ - 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)] + trimmed = [] + for module in modules: + trimmed.append(MODULE_NAMESPACE.sub('', module)) + return trimmed - # render <uses-library> tags as XML for a pretty error message - tags = [] - for lib in libs: - tags.append(lib.toprettyxml()) - required = first_unique_elements(required) - optional = first_unique_elements(optional) - tags = first_unique_elements(tags) - return required, optional, tags +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) is 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): #pylint: disable=inconsistent-return-statements + """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: #pylint: disable=no-else-raise + raise RuntimeError('found multiple <application> tags') + elif not elems: + if uses_libraries or optional_uses_libraries: #pylint: disable=undefined-variable + raise ManifestMismatchError('no <application> tag found') + return + + libs = get_children_with_tag(application, 'uses-library') + + 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()) + + required = first_unique_elements(required) + optional = first_unique_elements(optional) + tags = first_unique_elements(tags) + return required, optional, tags def first_unique_elements(l): - result = [] - [result.append(x) for x in l if x not in result] - return result + result = [] + for x in l: + if x not in result: + result.append(x) + return result def uses_library_name(lib): - """Extract the name attribute of a uses-library tag. + """Extract the name attribute of a uses-library tag. Args: lib: a <uses-library> tag. - """ - name = lib.getAttributeNodeNS(android_ns, 'name') - return name.value if name is not None else "" + """ + name = lib.getAttributeNodeNS(android_ns, 'name') + return name.value if name is not None else '' def uses_library_required(lib): - """Extract the required attribute of a uses-library tag. + """Extract the required attribute of a uses-library tag. Args: lib: a <uses-library> tag. - """ - required = lib.getAttributeNodeNS(android_ns, 'required') - return (required.value == 'true') if required is not None else True + """ + required = lib.getAttributeNodeNS(android_ns, 'required') + return (required.value == 'true') if required is not None else True -def extract_target_sdk_version(manifest, is_apk = False): - """Returns the targetSdkVersion from the manifest. +def extract_target_sdk_version(manifest, is_apk=False): + """Returns the targetSdkVersion from the manifest. Args: 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) + """ + if is_apk: #pylint: disable=no-else-return + 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.""" + """Extract targetSdkVersion tags from the manifest of an APK.""" - pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE) + pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE) - for match in re.finditer(pattern, badging): - return match.group(1) + for match in re.finditer(pattern, badging): + return match.group(1) - raise RuntimeError('cannot find targetSdkVersion in the manifest') + raise RuntimeError('cannot find targetSdkVersion in the manifest') def extract_target_sdk_version_xml(xml): - """Extract targetSdkVersion tags from the manifest.""" + """Extract targetSdkVersion tags from the manifest.""" - manifest = parse_manifest(xml) + manifest = parse_manifest(xml) - # Get or insert the uses-sdk element - uses_sdk = get_children_with_tag(manifest, 'uses-sdk') - if len(uses_sdk) > 1: - raise RuntimeError('found multiple uses-sdk elements') - elif len(uses_sdk) == 0: - raise RuntimeError('missing uses-sdk element') + # Get or insert the uses-sdk element + uses_sdk = get_children_with_tag(manifest, 'uses-sdk') + if len(uses_sdk) > 1: #pylint: disable=no-else-raise + raise RuntimeError('found multiple uses-sdk elements') + elif len(uses_sdk) == 0: + raise RuntimeError('missing uses-sdk element') - uses_sdk = uses_sdk[0] + uses_sdk = uses_sdk[0] - min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion') - if min_attr is None: - raise RuntimeError('minSdkVersion is not specified') + min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion') + if min_attr is None: + raise RuntimeError('minSdkVersion is not specified') - target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion') - if target_attr is None: - target_attr = min_attr + target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion') + if target_attr is None: + target_attr = min_attr - return target_attr.value + return target_attr.value def load_dexpreopt_configs(configs): - """Load dexpreopt.config files and map module names to library names.""" - module_to_libname = {} + """Load dexpreopt.config files and map module names to library names.""" + module_to_libname = {} - if configs is None: - configs = [] + 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'] + 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 + 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 = [] + """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) + libnames = [] + for name in modules: + if name in module_to_libname: + name = module_to_libname[name] + libnames.append(name) - return libnames + return libnames def main(): - """Program entry point.""" - try: - args = parse_args() + """Program entry point.""" + try: + args = parse_args() + + # 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 is not None else 'aapt' + manifest = subprocess.check_output( + [aapt, 'dump', 'badging', args.input]) + else: + manifest = minidom.parse(args.input) + + if args.enforce_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 is not None: + f.write('%s\n' % errmsg) + + if args.extract_target_sdk_version: + try: + print(extract_target_sdk_version(manifest, is_apk)) + except: #pylint: disable=bare-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, manifest) + + # pylint: disable=broad-except + except Exception as err: + print('error: ' + str(err), file=sys.stderr) + sys.exit(-1) - # 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: - # 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: - 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, manifest) - - # pylint: disable=broad-except - except Exception as err: - print('error: ' + str(err), file=sys.stderr) - sys.exit(-1) if __name__ == '__main__': - main() + main() |