| #!/usr/bin/env python |
| # |
| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Creates an AndroidManifest.xml for an incremental APK. |
| |
| Given the manifest file for the real APK, generates an AndroidManifest.xml with |
| the application class changed to IncrementalApplication. |
| """ |
| |
| import argparse |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| import zipfile |
| from xml.etree import ElementTree |
| |
| sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, 'gyp')) |
| from util import build_utils |
| from util import manifest_utils |
| from util import resource_utils |
| |
| _INCREMENTAL_APP_NAME = 'org.chromium.incrementalinstall.BootstrapApplication' |
| _META_DATA_APP_NAME = 'incremental-install-real-app' |
| _DEFAULT_APPLICATION_CLASS = 'android.app.Application' |
| _META_DATA_INSTRUMENTATION_NAMES = [ |
| 'incremental-install-real-instrumentation-0', |
| 'incremental-install-real-instrumentation-1', |
| ] |
| _INCREMENTAL_INSTRUMENTATION_CLASSES = [ |
| 'android.app.Instrumentation', |
| 'org.chromium.incrementalinstall.SecondInstrumentation', |
| ] |
| |
| |
| def _AddNamespace(name): |
| """Adds the android namespace prefix to the given identifier.""" |
| return '{%s}%s' % (manifest_utils.ANDROID_NAMESPACE, name) |
| |
| |
| def _ParseArgs(args): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--src-manifest', required=True, help='The main manifest of the app') |
| parser.add_argument('--disable-isolated-processes', |
| help='Changes all android:isolatedProcess to false. ' |
| 'This is required on Android M+', |
| action='store_true') |
| parser.add_argument( |
| '--out-apk', required=True, help='Path to output .ap_ file') |
| parser.add_argument( |
| '--in-apk', required=True, help='Path to non-incremental .ap_ file') |
| parser.add_argument( |
| '--aapt2-path', required=True, help='Path to the Android aapt tool') |
| parser.add_argument( |
| '--android-sdk-jars', help='GN List of resource apks to include.') |
| |
| ret = parser.parse_args(build_utils.ExpandFileArgs(args)) |
| ret.android_sdk_jars = build_utils.ParseGnList(ret.android_sdk_jars) |
| return ret |
| |
| |
| def _CreateMetaData(parent, name, value): |
| meta_data_node = ElementTree.SubElement(parent, 'meta-data') |
| meta_data_node.set(_AddNamespace('name'), name) |
| meta_data_node.set(_AddNamespace('value'), value) |
| |
| |
| def _ProcessManifest(path, arsc_package_name, disable_isolated_processes): |
| doc, manifest_node, app_node = manifest_utils.ParseManifest(path) |
| |
| # Ensure the manifest package matches that of the apk's arsc package |
| # So that resource references resolve correctly. The actual manifest |
| # package name is set via --rename-manifest-package. |
| manifest_node.set('package', arsc_package_name) |
| |
| # Pylint for some reason things app_node is an int. |
| # pylint: disable=no-member |
| real_app_class = app_node.get(_AddNamespace('name'), |
| _DEFAULT_APPLICATION_CLASS) |
| app_node.set(_AddNamespace('name'), _INCREMENTAL_APP_NAME) |
| # pylint: enable=no-member |
| _CreateMetaData(app_node, _META_DATA_APP_NAME, real_app_class) |
| |
| # Seems to be a bug in ElementTree, as doc.find() doesn't work here. |
| instrumentation_nodes = doc.findall('instrumentation') |
| assert len(instrumentation_nodes) <= 2, ( |
| 'Need to update incremental install to support >2 <instrumentation> tags') |
| for i, instrumentation_node in enumerate(instrumentation_nodes): |
| real_instrumentation_class = instrumentation_node.get(_AddNamespace('name')) |
| instrumentation_node.set(_AddNamespace('name'), |
| _INCREMENTAL_INSTRUMENTATION_CLASSES[i]) |
| _CreateMetaData(app_node, _META_DATA_INSTRUMENTATION_NAMES[i], |
| real_instrumentation_class) |
| |
| ret = ElementTree.tostring(doc.getroot(), encoding='UTF-8') |
| # Disable check for page-aligned native libraries. |
| ret = ret.replace(b'extractNativeLibs="false"', b'extractNativeLibs="true"') |
| if disable_isolated_processes: |
| ret = ret.replace(b'isolatedProcess="true"', b'isolatedProcess="false"') |
| return ret |
| |
| |
| def main(raw_args): |
| options = _ParseArgs(raw_args) |
| |
| arsc_package, _ = resource_utils.ExtractArscPackage(options.aapt2_path, |
| options.in_apk) |
| # Extract version from the compiled manifest since it might have been set |
| # via aapt, and not exist in the manifest's text form. |
| version_code, version_name, manifest_package = ( |
| resource_utils.ExtractBinaryManifestValues(options.aapt2_path, |
| options.in_apk)) |
| |
| new_manifest_data = _ProcessManifest(options.src_manifest, arsc_package, |
| options.disable_isolated_processes) |
| with tempfile.NamedTemporaryFile() as tmp_manifest, \ |
| tempfile.NamedTemporaryFile() as tmp_apk: |
| tmp_manifest.write(new_manifest_data) |
| tmp_manifest.flush() |
| cmd = [ |
| options.aapt2_path, 'link', '-o', tmp_apk.name, '--manifest', |
| tmp_manifest.name, '-I', options.in_apk, '--replace-version', |
| '--version-code', version_code, '--version-name', version_name, |
| '--rename-manifest-package', manifest_package, '--debug-mode' |
| ] |
| for j in options.android_sdk_jars: |
| cmd += ['-I', j] |
| subprocess.check_call(cmd) |
| with zipfile.ZipFile(options.out_apk, 'w') as z: |
| path_transform = lambda p: None if p != 'AndroidManifest.xml' else p |
| build_utils.MergeZips(z, [tmp_apk.name], path_transform=path_transform) |
| path_transform = lambda p: None if p == 'AndroidManifest.xml' else p |
| build_utils.MergeZips(z, [options.in_apk], path_transform=path_transform) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1:]) |