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