| Colin Cross | 7211910 | 2019-05-20 13:14:18 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python | 
|  | 2 | # | 
|  | 3 | # Copyright (C) 2018 The Android Open Source Project | 
|  | 4 | # | 
|  | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 6 | # you may not use this file except in compliance with the License. | 
|  | 7 | # You may obtain a copy of the License at | 
|  | 8 | # | 
|  | 9 | #      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 10 | # | 
|  | 11 | # Unless required by applicable law or agreed to in writing, software | 
|  | 12 | # distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 14 | # See the License for the specific language governing permissions and | 
|  | 15 | # limitations under the License. | 
|  | 16 | # | 
|  | 17 | """A tool for checking that a manifest agrees with the build system.""" | 
|  | 18 |  | 
|  | 19 | from __future__ import print_function | 
|  | 20 |  | 
|  | 21 | import argparse | 
|  | 22 | import sys | 
|  | 23 | from xml.dom import minidom | 
|  | 24 |  | 
|  | 25 |  | 
|  | 26 | from manifest import android_ns | 
|  | 27 | from manifest import get_children_with_tag | 
|  | 28 | from manifest import parse_manifest | 
|  | 29 | from manifest import write_xml | 
|  | 30 |  | 
|  | 31 |  | 
|  | 32 | class ManifestMismatchError(Exception): | 
|  | 33 | pass | 
|  | 34 |  | 
|  | 35 |  | 
|  | 36 | def parse_args(): | 
|  | 37 | """Parse commandline arguments.""" | 
|  | 38 |  | 
|  | 39 | parser = argparse.ArgumentParser() | 
|  | 40 | parser.add_argument('--uses-library', dest='uses_libraries', | 
|  | 41 | action='append', | 
|  | 42 | help='specify uses-library entries known to the build system') | 
|  | 43 | parser.add_argument('--optional-uses-library', | 
|  | 44 | dest='optional_uses_libraries', | 
|  | 45 | action='append', | 
|  | 46 | help='specify uses-library entries known to the build system with required:false') | 
|  | 47 | parser.add_argument('--enforce-uses-libraries', | 
|  | 48 | dest='enforce_uses_libraries', | 
|  | 49 | action='store_true', | 
|  | 50 | help='check the uses-library entries known to the build system against the manifest') | 
|  | 51 | parser.add_argument('--extract-target-sdk-version', | 
|  | 52 | dest='extract_target_sdk_version', | 
|  | 53 | action='store_true', | 
|  | 54 | help='print the targetSdkVersion from the manifest') | 
|  | 55 | parser.add_argument('--output', '-o', dest='output', help='output AndroidManifest.xml file') | 
|  | 56 | parser.add_argument('input', help='input AndroidManifest.xml file') | 
|  | 57 | return parser.parse_args() | 
|  | 58 |  | 
|  | 59 |  | 
|  | 60 | def enforce_uses_libraries(doc, uses_libraries, optional_uses_libraries): | 
|  | 61 | """Verify that the <uses-library> tags in the manifest match those provided by the build system. | 
|  | 62 |  | 
|  | 63 | Args: | 
|  | 64 | doc: The XML document. | 
|  | 65 | uses_libraries: The names of <uses-library> tags known to the build system | 
|  | 66 | optional_uses_libraries: The names of <uses-library> tags with required:fals | 
|  | 67 | known to the build system | 
|  | 68 | Raises: | 
|  | 69 | RuntimeError: Invalid manifest | 
|  | 70 | ManifestMismatchError: Manifest does not match | 
|  | 71 | """ | 
|  | 72 |  | 
|  | 73 | manifest = parse_manifest(doc) | 
|  | 74 | elems = get_children_with_tag(manifest, 'application') | 
|  | 75 | application = elems[0] if len(elems) == 1 else None | 
|  | 76 | if len(elems) > 1: | 
|  | 77 | raise RuntimeError('found multiple <application> tags') | 
|  | 78 | elif not elems: | 
|  | 79 | if uses_libraries or optional_uses_libraries: | 
|  | 80 | raise ManifestMismatchError('no <application> tag found') | 
|  | 81 | return | 
|  | 82 |  | 
|  | 83 | verify_uses_library(application, uses_libraries, optional_uses_libraries) | 
|  | 84 |  | 
|  | 85 |  | 
|  | 86 | def verify_uses_library(application, uses_libraries, optional_uses_libraries): | 
|  | 87 | """Verify that the uses-library values known to the build system match the manifest. | 
|  | 88 |  | 
|  | 89 | Args: | 
|  | 90 | application: the <application> tag in the manifest. | 
|  | 91 | uses_libraries: the names of expected <uses-library> tags. | 
|  | 92 | optional_uses_libraries: the names of expected <uses-library> tags with required="false". | 
|  | 93 | Raises: | 
|  | 94 | ManifestMismatchError: Manifest does not match | 
|  | 95 | """ | 
|  | 96 |  | 
|  | 97 | if uses_libraries is None: | 
|  | 98 | uses_libraries = [] | 
|  | 99 |  | 
|  | 100 | if optional_uses_libraries is None: | 
|  | 101 | optional_uses_libraries = [] | 
|  | 102 |  | 
|  | 103 | manifest_uses_libraries, manifest_optional_uses_libraries = parse_uses_library(application) | 
|  | 104 |  | 
|  | 105 | err = [] | 
|  | 106 | if manifest_uses_libraries != uses_libraries: | 
|  | 107 | err.append('Expected required <uses-library> tags "%s", got "%s"' % | 
|  | 108 | (', '.join(uses_libraries), ', '.join(manifest_uses_libraries))) | 
|  | 109 |  | 
|  | 110 | if manifest_optional_uses_libraries != optional_uses_libraries: | 
|  | 111 | err.append('Expected optional <uses-library> tags "%s", got "%s"' % | 
|  | 112 | (', '.join(optional_uses_libraries), ', '.join(manifest_optional_uses_libraries))) | 
|  | 113 |  | 
|  | 114 | if err: | 
|  | 115 | raise ManifestMismatchError('\n'.join(err)) | 
|  | 116 |  | 
|  | 117 |  | 
|  | 118 | def parse_uses_library(application): | 
|  | 119 | """Extract uses-library tags from the manifest. | 
|  | 120 |  | 
|  | 121 | Args: | 
|  | 122 | application: the <application> tag in the manifest. | 
|  | 123 | """ | 
|  | 124 |  | 
|  | 125 | libs = get_children_with_tag(application, 'uses-library') | 
|  | 126 |  | 
|  | 127 | uses_libraries = [uses_library_name(x) for x in libs if uses_library_required(x)] | 
|  | 128 | optional_uses_libraries = [uses_library_name(x) for x in libs if not uses_library_required(x)] | 
|  | 129 |  | 
|  | 130 | return first_unique_elements(uses_libraries), first_unique_elements(optional_uses_libraries) | 
|  | 131 |  | 
|  | 132 |  | 
|  | 133 | def first_unique_elements(l): | 
|  | 134 | result = [] | 
|  | 135 | [result.append(x) for x in l if x not in result] | 
|  | 136 | return result | 
|  | 137 |  | 
|  | 138 |  | 
|  | 139 | def uses_library_name(lib): | 
|  | 140 | """Extract the name attribute of a uses-library tag. | 
|  | 141 |  | 
|  | 142 | Args: | 
|  | 143 | lib: a <uses-library> tag. | 
|  | 144 | """ | 
|  | 145 | name = lib.getAttributeNodeNS(android_ns, 'name') | 
|  | 146 | return name.value if name is not None else "" | 
|  | 147 |  | 
|  | 148 |  | 
|  | 149 | def uses_library_required(lib): | 
|  | 150 | """Extract the required attribute of a uses-library tag. | 
|  | 151 |  | 
|  | 152 | Args: | 
|  | 153 | lib: a <uses-library> tag. | 
|  | 154 | """ | 
|  | 155 | required = lib.getAttributeNodeNS(android_ns, 'required') | 
|  | 156 | return (required.value == 'true') if required is not None else True | 
|  | 157 |  | 
|  | 158 |  | 
|  | 159 | def extract_target_sdk_version(doc): | 
|  | 160 | """Returns the targetSdkVersion from the manifest. | 
|  | 161 |  | 
|  | 162 | Args: | 
|  | 163 | doc: The XML document. | 
|  | 164 | Raises: | 
|  | 165 | RuntimeError: invalid manifest | 
|  | 166 | """ | 
|  | 167 |  | 
|  | 168 | manifest = parse_manifest(doc) | 
|  | 169 |  | 
|  | 170 | # Get or insert the uses-sdk element | 
|  | 171 | uses_sdk = get_children_with_tag(manifest, 'uses-sdk') | 
|  | 172 | if len(uses_sdk) > 1: | 
|  | 173 | raise RuntimeError('found multiple uses-sdk elements') | 
|  | 174 | elif len(uses_sdk) == 0: | 
|  | 175 | raise RuntimeError('missing uses-sdk element') | 
|  | 176 |  | 
|  | 177 | uses_sdk = uses_sdk[0] | 
|  | 178 |  | 
|  | 179 | min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion') | 
|  | 180 | if min_attr is None: | 
|  | 181 | raise RuntimeError('minSdkVersion is not specified') | 
|  | 182 |  | 
|  | 183 | target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion') | 
|  | 184 | if target_attr is None: | 
|  | 185 | target_attr = min_attr | 
|  | 186 |  | 
|  | 187 | return target_attr.value | 
|  | 188 |  | 
|  | 189 |  | 
|  | 190 | def main(): | 
|  | 191 | """Program entry point.""" | 
|  | 192 | try: | 
|  | 193 | args = parse_args() | 
|  | 194 |  | 
|  | 195 | doc = minidom.parse(args.input) | 
|  | 196 |  | 
|  | 197 | if args.enforce_uses_libraries: | 
|  | 198 | enforce_uses_libraries(doc, | 
|  | 199 | args.uses_libraries, | 
|  | 200 | args.optional_uses_libraries) | 
|  | 201 |  | 
|  | 202 | if args.extract_target_sdk_version: | 
|  | 203 | print(extract_target_sdk_version(doc)) | 
|  | 204 |  | 
|  | 205 | if args.output: | 
|  | 206 | with open(args.output, 'wb') as f: | 
|  | 207 | write_xml(f, doc) | 
|  | 208 |  | 
|  | 209 | # pylint: disable=broad-except | 
|  | 210 | except Exception as err: | 
|  | 211 | print('error: ' + str(err), file=sys.stderr) | 
|  | 212 | sys.exit(-1) | 
|  | 213 |  | 
|  | 214 | if __name__ == '__main__': | 
|  | 215 | main() |