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 19from __future__ import print_function 20 21import argparse 22import sys 23from xml.dom import minidom 24 25 26from manifest import android_ns 27from manifest import get_children_with_tag 28from manifest import parse_manifest 29from manifest import write_xml 30 31 32class ManifestMismatchError(Exception): 33 pass 34 35 36def 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 60def 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 86def 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 118def 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 133def first_unique_elements(l): 134 result = [] 135 [result.append(x) for x in l if x not in result] 136 return result 137 138 139def 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 149def 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 159def 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 190def 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 214if __name__ == '__main__': 215 main() 216