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 inserting values from the build system into a manifest.""" 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 compare_version_gt 28from manifest import ensure_manifest_android_ns 29from manifest import find_child_with_attribute 30from manifest import get_children_with_tag 31from manifest import get_indent 32from manifest import parse_manifest 33from manifest import write_xml 34 35 36def parse_args(): 37 """Parse commandline arguments.""" 38 39 parser = argparse.ArgumentParser() 40 parser.add_argument('--minSdkVersion', default='', dest='min_sdk_version', 41 help='specify minSdkVersion used by the build system') 42 parser.add_argument('--targetSdkVersion', default='', dest='target_sdk_version', 43 help='specify targetSdkVersion used by the build system') 44 parser.add_argument('--raise-min-sdk-version', dest='raise_min_sdk_version', action='store_true', 45 help='raise the minimum sdk version in the manifest if necessary') 46 parser.add_argument('--library', dest='library', action='store_true', 47 help='manifest is for a static library') 48 parser.add_argument('--uses-library', dest='uses_libraries', action='append', 49 help='specify additional <uses-library> tag to add. android:requred is set to true') 50 parser.add_argument('--optional-uses-library', dest='optional_uses_libraries', action='append', 51 help='specify additional <uses-library> tag to add. android:requred is set to false') 52 parser.add_argument('--uses-non-sdk-api', dest='uses_non_sdk_api', action='store_true', 53 help='manifest is for a package built against the platform') 54 parser.add_argument('--logging-parent', dest='logging_parent', default='', 55 help=('specify logging parent as an additional <meta-data> tag. ' 56 'This value is ignored if the logging_parent meta-data tag is present.')) 57 parser.add_argument('--use-embedded-dex', dest='use_embedded_dex', action='store_true', 58 help=('specify if the app wants to use embedded dex and avoid extracted,' 59 'locally compiled code. Must not conflict if already declared ' 60 'in the manifest.')) 61 parser.add_argument('--extract-native-libs', dest='extract_native_libs', 62 default=None, type=lambda x: (str(x).lower() == 'true'), 63 help=('specify if the app wants to use embedded native libraries. Must not conflict ' 64 'if already declared in the manifest.')) 65 parser.add_argument('--has-no-code', dest='has_no_code', action='store_true', 66 help=('adds hasCode="false" attribute to application. Ignored if application elem ' 67 'already has a hasCode attribute.')) 68 parser.add_argument('input', help='input AndroidManifest.xml file') 69 parser.add_argument('output', help='output AndroidManifest.xml file') 70 return parser.parse_args() 71 72 73def raise_min_sdk_version(doc, min_sdk_version, target_sdk_version, library): 74 """Ensure the manifest contains a <uses-sdk> tag with a minSdkVersion. 75 76 Args: 77 doc: The XML document. May be modified by this function. 78 min_sdk_version: The requested minSdkVersion attribute. 79 target_sdk_version: The requested targetSdkVersion attribute. 80 library: True if the manifest is for a library. 81 Raises: 82 RuntimeError: invalid manifest 83 """ 84 85 manifest = parse_manifest(doc) 86 87 # Get or insert the uses-sdk element 88 uses_sdk = get_children_with_tag(manifest, 'uses-sdk') 89 if len(uses_sdk) > 1: 90 raise RuntimeError('found multiple uses-sdk elements') 91 elif len(uses_sdk) == 1: 92 element = uses_sdk[0] 93 else: 94 element = doc.createElement('uses-sdk') 95 indent = get_indent(manifest.firstChild, 1) 96 manifest.insertBefore(element, manifest.firstChild) 97 98 # Insert an indent before uses-sdk to line it up with the indentation of the 99 # other children of the <manifest> tag. 100 manifest.insertBefore(doc.createTextNode(indent), manifest.firstChild) 101 102 # Get or insert the minSdkVersion attribute. If it is already present, make 103 # sure it as least the requested value. 104 min_attr = element.getAttributeNodeNS(android_ns, 'minSdkVersion') 105 if min_attr is None: 106 min_attr = doc.createAttributeNS(android_ns, 'android:minSdkVersion') 107 min_attr.value = min_sdk_version 108 element.setAttributeNode(min_attr) 109 else: 110 if compare_version_gt(min_sdk_version, min_attr.value): 111 min_attr.value = min_sdk_version 112 113 # Insert the targetSdkVersion attribute if it is missing. If it is already 114 # present leave it as is. 115 target_attr = element.getAttributeNodeNS(android_ns, 'targetSdkVersion') 116 if target_attr is None: 117 target_attr = doc.createAttributeNS(android_ns, 'android:targetSdkVersion') 118 if library: 119 # TODO(b/117122200): libraries shouldn't set targetSdkVersion at all, but 120 # ManifestMerger treats minSdkVersion="Q" as targetSdkVersion="Q" if it 121 # is empty. Set it to something low so that it will be overriden by the 122 # main manifest, but high enough that it doesn't cause implicit 123 # permissions grants. 124 target_attr.value = '16' 125 else: 126 target_attr.value = target_sdk_version 127 element.setAttributeNode(target_attr) 128 129 130def add_logging_parent(doc, logging_parent_value): 131 """Add logging parent as an additional <meta-data> tag. 132 133 Args: 134 doc: The XML document. May be modified by this function. 135 logging_parent_value: A string representing the logging 136 parent value. 137 Raises: 138 RuntimeError: Invalid manifest 139 """ 140 manifest = parse_manifest(doc) 141 142 logging_parent_key = 'android.content.pm.LOGGING_PARENT' 143 elems = get_children_with_tag(manifest, 'application') 144 application = elems[0] if len(elems) == 1 else None 145 if len(elems) > 1: 146 raise RuntimeError('found multiple <application> tags') 147 elif not elems: 148 application = doc.createElement('application') 149 indent = get_indent(manifest.firstChild, 1) 150 first = manifest.firstChild 151 manifest.insertBefore(doc.createTextNode(indent), first) 152 manifest.insertBefore(application, first) 153 154 indent = get_indent(application.firstChild, 2) 155 156 last = application.lastChild 157 if last is not None and last.nodeType != minidom.Node.TEXT_NODE: 158 last = None 159 160 if not find_child_with_attribute(application, 'meta-data', android_ns, 161 'name', logging_parent_key): 162 ul = doc.createElement('meta-data') 163 ul.setAttributeNS(android_ns, 'android:name', logging_parent_key) 164 ul.setAttributeNS(android_ns, 'android:value', logging_parent_value) 165 application.insertBefore(doc.createTextNode(indent), last) 166 application.insertBefore(ul, last) 167 last = application.lastChild 168 169 # align the closing tag with the opening tag if it's not 170 # indented 171 if last and last.nodeType != minidom.Node.TEXT_NODE: 172 indent = get_indent(application.previousSibling, 1) 173 application.appendChild(doc.createTextNode(indent)) 174 175 176def add_uses_libraries(doc, new_uses_libraries, required): 177 """Add additional <uses-library> tags 178 179 Args: 180 doc: The XML document. May be modified by this function. 181 new_uses_libraries: The names of libraries to be added by this function. 182 required: The value of android:required attribute. Can be true or false. 183 Raises: 184 RuntimeError: Invalid manifest 185 """ 186 187 manifest = parse_manifest(doc) 188 elems = get_children_with_tag(manifest, 'application') 189 application = elems[0] if len(elems) == 1 else None 190 if len(elems) > 1: 191 raise RuntimeError('found multiple <application> tags') 192 elif not elems: 193 application = doc.createElement('application') 194 indent = get_indent(manifest.firstChild, 1) 195 first = manifest.firstChild 196 manifest.insertBefore(doc.createTextNode(indent), first) 197 manifest.insertBefore(application, first) 198 199 indent = get_indent(application.firstChild, 2) 200 201 last = application.lastChild 202 if last is not None and last.nodeType != minidom.Node.TEXT_NODE: 203 last = None 204 205 for name in new_uses_libraries: 206 if find_child_with_attribute(application, 'uses-library', android_ns, 207 'name', name) is not None: 208 # If the uses-library tag of the same 'name' attribute value exists, 209 # respect it. 210 continue 211 212 ul = doc.createElement('uses-library') 213 ul.setAttributeNS(android_ns, 'android:name', name) 214 ul.setAttributeNS(android_ns, 'android:required', str(required).lower()) 215 216 application.insertBefore(doc.createTextNode(indent), last) 217 application.insertBefore(ul, last) 218 219 # align the closing tag with the opening tag if it's not 220 # indented 221 if application.lastChild.nodeType != minidom.Node.TEXT_NODE: 222 indent = get_indent(application.previousSibling, 1) 223 application.appendChild(doc.createTextNode(indent)) 224 225 226def add_uses_non_sdk_api(doc): 227 """Add android:usesNonSdkApi=true attribute to <application>. 228 229 Args: 230 doc: The XML document. May be modified by this function. 231 Raises: 232 RuntimeError: Invalid manifest 233 """ 234 235 manifest = parse_manifest(doc) 236 elems = get_children_with_tag(manifest, 'application') 237 application = elems[0] if len(elems) == 1 else None 238 if len(elems) > 1: 239 raise RuntimeError('found multiple <application> tags') 240 elif not elems: 241 application = doc.createElement('application') 242 indent = get_indent(manifest.firstChild, 1) 243 first = manifest.firstChild 244 manifest.insertBefore(doc.createTextNode(indent), first) 245 manifest.insertBefore(application, first) 246 247 attr = application.getAttributeNodeNS(android_ns, 'usesNonSdkApi') 248 if attr is None: 249 attr = doc.createAttributeNS(android_ns, 'android:usesNonSdkApi') 250 attr.value = 'true' 251 application.setAttributeNode(attr) 252 253 254def add_use_embedded_dex(doc): 255 manifest = parse_manifest(doc) 256 elems = get_children_with_tag(manifest, 'application') 257 application = elems[0] if len(elems) == 1 else None 258 if len(elems) > 1: 259 raise RuntimeError('found multiple <application> tags') 260 elif not elems: 261 application = doc.createElement('application') 262 indent = get_indent(manifest.firstChild, 1) 263 first = manifest.firstChild 264 manifest.insertBefore(doc.createTextNode(indent), first) 265 manifest.insertBefore(application, first) 266 267 attr = application.getAttributeNodeNS(android_ns, 'useEmbeddedDex') 268 if attr is None: 269 attr = doc.createAttributeNS(android_ns, 'android:useEmbeddedDex') 270 attr.value = 'true' 271 application.setAttributeNode(attr) 272 elif attr.value != 'true': 273 raise RuntimeError('existing attribute mismatches the option of --use-embedded-dex') 274 275 276def add_extract_native_libs(doc, extract_native_libs): 277 manifest = parse_manifest(doc) 278 elems = get_children_with_tag(manifest, 'application') 279 application = elems[0] if len(elems) == 1 else None 280 if len(elems) > 1: 281 raise RuntimeError('found multiple <application> tags') 282 elif not elems: 283 application = doc.createElement('application') 284 indent = get_indent(manifest.firstChild, 1) 285 first = manifest.firstChild 286 manifest.insertBefore(doc.createTextNode(indent), first) 287 manifest.insertBefore(application, first) 288 289 value = str(extract_native_libs).lower() 290 attr = application.getAttributeNodeNS(android_ns, 'extractNativeLibs') 291 if attr is None: 292 attr = doc.createAttributeNS(android_ns, 'android:extractNativeLibs') 293 attr.value = value 294 application.setAttributeNode(attr) 295 elif attr.value != value: 296 raise RuntimeError('existing attribute extractNativeLibs="%s" conflicts with --extract-native-libs="%s"' % 297 (attr.value, value)) 298 299 300def set_has_code_to_false(doc): 301 manifest = parse_manifest(doc) 302 elems = get_children_with_tag(manifest, 'application') 303 application = elems[0] if len(elems) == 1 else None 304 if len(elems) > 1: 305 raise RuntimeError('found multiple <application> tags') 306 elif not elems: 307 application = doc.createElement('application') 308 indent = get_indent(manifest.firstChild, 1) 309 first = manifest.firstChild 310 manifest.insertBefore(doc.createTextNode(indent), first) 311 manifest.insertBefore(application, first) 312 313 attr = application.getAttributeNodeNS(android_ns, 'hasCode') 314 if attr is not None: 315 # Do nothing if the application already has a hasCode attribute. 316 return 317 attr = doc.createAttributeNS(android_ns, 'android:hasCode') 318 attr.value = 'false' 319 application.setAttributeNode(attr) 320 321 322def main(): 323 """Program entry point.""" 324 try: 325 args = parse_args() 326 327 doc = minidom.parse(args.input) 328 329 ensure_manifest_android_ns(doc) 330 331 if args.raise_min_sdk_version: 332 raise_min_sdk_version(doc, args.min_sdk_version, args.target_sdk_version, args.library) 333 334 if args.uses_libraries: 335 add_uses_libraries(doc, args.uses_libraries, True) 336 337 if args.optional_uses_libraries: 338 add_uses_libraries(doc, args.optional_uses_libraries, False) 339 340 if args.uses_non_sdk_api: 341 add_uses_non_sdk_api(doc) 342 343 if args.logging_parent: 344 add_logging_parent(doc, args.logging_parent) 345 346 if args.use_embedded_dex: 347 add_use_embedded_dex(doc) 348 349 if args.has_no_code: 350 set_has_code_to_false(doc) 351 352 if args.extract_native_libs is not None: 353 add_extract_native_libs(doc, args.extract_native_libs) 354 355 with open(args.output, 'wb') as f: 356 write_xml(f, doc) 357 358 # pylint: disable=broad-except 359 except Exception as err: 360 print('error: ' + str(err), file=sys.stderr) 361 sys.exit(-1) 362 363if __name__ == '__main__': 364 main() 365