blob: f2e8a48f0fe89e83fa1d65e3ac7e5f3a587a9dcf [file] [log] [blame]
# pylint: disable=g-direct-third-party-import
# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Stubifies an AndroidManifest.xml.
Does the following things:
- Replaces the Application class in an Android manifest with a stub one
- Resolve string and integer resources to their default values
usage: %s [input manifest] [output manifest] [file for old application class]
Writes the old application class into the file designated by the third argument.
"""
from __future__ import print_function
import sys
from xml.etree import ElementTree
from third_party.py import gflags
gflags.DEFINE_string("mode", "mobile_install",
"mobile_install or instant_run mode")
gflags.DEFINE_string("input_manifest", None, "The input manifest")
gflags.DEFINE_string("output_manifest", None, "The output manifest")
gflags.DEFINE_string("output_datafile", None, "The output data file that will "
"be embedded in the final APK")
gflags.DEFINE_string("override_package", None,
"The Android package. Override the one specified in the "
"input manifest")
FLAGS = gflags.FLAGS
ANDROID = "http://schemas.android.com/apk/res/android"
READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"
MOBILE_INSTALL_STUB_APPLICATION = (
"com.google.devtools.build.android.incrementaldeployment.StubApplication")
INSTANT_RUN_BOOTSTRAP_APPLICATION = (
"com.android.tools.fd.runtime.BootstrapApplication")
# This is global state, but apparently that's the best one can to with
# ElementTree :(
ElementTree.register_namespace("android", ANDROID)
class BadManifestException(Exception):
pass
def StubifyMobileInstall(manifest_string):
"""Does the stubification on an XML string for mobile-install.
Args:
manifest_string: the input manifest as a string.
Returns:
A tuple of (output manifest, old application class, app package)
Raises:
Exception: if something goes wrong
"""
manifest, application = _ParseManifest(manifest_string)
old_application = application.get(
"{%s}name" % ANDROID, "android.app.Application")
application.set("{%s}name" % ANDROID, MOBILE_INSTALL_STUB_APPLICATION)
application.attrib.pop("{%s}hasCode" % ANDROID, None)
read_permission = manifest.findall(
'./uses-permission[@android:name="%s"]' % READ_EXTERNAL_STORAGE,
namespaces={"android": ANDROID})
if not read_permission:
read_permission = ElementTree.Element("uses-permission")
read_permission.set("{%s}name" % ANDROID, READ_EXTERNAL_STORAGE)
manifest.insert(0, read_permission)
new_manifest = ElementTree.tostring(manifest)
app_package = manifest.get("package")
if not app_package:
raise BadManifestException("manifest tag does not have a package specified")
return (new_manifest, old_application, app_package)
def StubifyInstantRun(manifest_string):
"""Stubifies the manifest for Instant Run.
Args:
manifest_string: the input manifest as a string.
Returns:
The new manifest as a string.
Raises:
Exception: if something goes wrong
"""
manifest, application = _ParseManifest(manifest_string)
old_application = application.get("{%s}name" % ANDROID)
if old_application:
application.set("name", old_application)
application.set("{%s}name" % ANDROID, INSTANT_RUN_BOOTSTRAP_APPLICATION)
return ElementTree.tostring(manifest)
def _ParseManifest(manifest_string):
"""Parses the given manifest xml.
Args:
manifest_string: the manifest as a string.
Returns:
a tuple of the manifest ElementTree and the application tag.
Raises:
BadManifestException: if the manifest is bad.
"""
manifest = ElementTree.fromstring(manifest_string)
if manifest.tag != "manifest":
raise BadManifestException("invalid input manifest")
app_list = manifest.findall("application")
if len(app_list) == 1:
# <application> element is present
application = app_list[0]
elif len(app_list) == 0: # pylint: disable=g-explicit-length-test
# <application> element is not present
application = ElementTree.Element("application")
manifest.insert(0, application)
else:
raise BadManifestException("multiple <application> elements present")
return (manifest, application)
def main():
if FLAGS.mode == "mobile_install":
with open(FLAGS.input_manifest, "rb") as input_manifest:
new_manifest, old_application, app_package = (
StubifyMobileInstall(input_manifest.read()))
if FLAGS.override_package:
app_package = FLAGS.override_package
with open(FLAGS.output_manifest, "wb") as output_xml:
output_xml.write(new_manifest)
with open(FLAGS.output_datafile, "wb") as output_file:
output_file.write("\n".join([old_application, app_package]).encode())
elif FLAGS.mode == "instant_run":
with open(FLAGS.input_manifest, "rb") as input_manifest:
new_manifest = StubifyInstantRun(input_manifest.read())
with open(FLAGS.output_manifest, "wb") as output_xml:
output_xml.write(new_manifest)
if __name__ == "__main__":
FLAGS(sys.argv)
try:
main()
except BadManifestException as e:
print(e)
sys.exit(1)