Files
build_soong/scripts/manifest_fixer.py
Jiyong Park fa17afef53 android.test.{base|mock} are added as optional libraries.
The two java shared libraries do not exist in the pre-P devices.
Therefore, marking the dependencies to the libraries as optional so that
an app is still installable to the old devices.

This is safe because the classes in the java shared libs exist in other
shared libraries (bootclasspath or android.test.runner).

Bug: 113800422
Test: run aapt dump badging on the built GtsBackupHostTestCases

uses-library tags for android.test.base|mock are with
android:required=false, while the same tag for other libs are without
the attribute.

Change-Id: I266c8efb9686e8975be37f31aea0dc22f82370c3
2018-10-16 18:05:49 +09:00

309 lines
10 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (C) 2018 The Android Open Source Project
#
# 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.
#
"""A tool for inserting values from the build system into a manifest."""
from __future__ import print_function
import argparse
import sys
from xml.dom import minidom
android_ns = 'http://schemas.android.com/apk/res/android'
def get_children_with_tag(parent, tag_name):
children = []
for child in parent.childNodes:
if child.nodeType == minidom.Node.ELEMENT_NODE and \
child.tagName == tag_name:
children.append(child)
return children
def find_child_with_attribute(element, tag_name, namespace_uri,
attr_name, value):
for child in get_children_with_tag(element, tag_name):
attr = child.getAttributeNodeNS(namespace_uri, attr_name)
if attr is not None and attr.value == value:
return child
return None
def parse_args():
"""Parse commandline arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--minSdkVersion', default='', dest='min_sdk_version',
help='specify minSdkVersion used by the build system')
parser.add_argument('--targetSdkVersion', default='', dest='target_sdk_version',
help='specify targetSdkVersion used by the build system')
parser.add_argument('--raise-min-sdk-version', dest='raise_min_sdk_version', action='store_true',
help='raise the minimum sdk version in the manifest if necessary')
parser.add_argument('--library', dest='library', action='store_true',
help='manifest is for a static library')
parser.add_argument('--uses-library', dest='uses_libraries', action='append',
help='specify additional <uses-library> tag to add. android:requred is set to true')
parser.add_argument('--optional-uses-library', dest='optional_uses_libraries', action='append',
help='specify additional <uses-library> tag to add. android:requred is set to false')
parser.add_argument('--uses-non-sdk-api', dest='uses_non_sdk_api', action='store_true',
help='manifest is for a package built against the platform')
parser.add_argument('input', help='input AndroidManifest.xml file')
parser.add_argument('output', help='output AndroidManifest.xml file')
return parser.parse_args()
def parse_manifest(doc):
"""Get the manifest element."""
manifest = doc.documentElement
if manifest.tagName != 'manifest':
raise RuntimeError('expected manifest tag at root')
return manifest
def ensure_manifest_android_ns(doc):
"""Make sure the manifest tag defines the android namespace."""
manifest = parse_manifest(doc)
ns = manifest.getAttributeNodeNS(minidom.XMLNS_NAMESPACE, 'android')
if ns is None:
attr = doc.createAttributeNS(minidom.XMLNS_NAMESPACE, 'xmlns:android')
attr.value = android_ns
manifest.setAttributeNode(attr)
elif ns.value != android_ns:
raise RuntimeError('manifest tag has incorrect android namespace ' +
ns.value)
def as_int(s):
try:
i = int(s)
except ValueError:
return s, False
return i, True
def compare_version_gt(a, b):
"""Compare two SDK versions.
Compares a and b, treating codenames like 'Q' as higher
than numerical versions like '28'.
Returns True if a > b
Args:
a: value to compare
b: value to compare
Returns:
True if a is a higher version than b
"""
a, a_is_int = as_int(a.upper())
b, b_is_int = as_int(b.upper())
if a_is_int == b_is_int:
# Both are codenames or both are versions, compare directly
return a > b
else:
# One is a codename, the other is not. Return true if
# b is an integer version
return b_is_int
def get_indent(element, default_level):
indent = ''
if element is not None and element.nodeType == minidom.Node.TEXT_NODE:
text = element.nodeValue
indent = text[:len(text)-len(text.lstrip())]
if not indent or indent == '\n':
# 1 indent = 4 space
indent = '\n' + (' ' * default_level * 4)
return indent
def raise_min_sdk_version(doc, min_sdk_version, target_sdk_version, library):
"""Ensure the manifest contains a <uses-sdk> tag with a minSdkVersion.
Args:
doc: The XML document. May be modified by this function.
min_sdk_version: The requested minSdkVersion attribute.
target_sdk_version: The requested targetSdkVersion attribute.
Raises:
RuntimeError: invalid manifest
"""
manifest = parse_manifest(doc)
# Get or insert the uses-sdk element
uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
if len(uses_sdk) > 1:
raise RuntimeError('found multiple uses-sdk elements')
elif len(uses_sdk) == 1:
element = uses_sdk[0]
else:
element = doc.createElement('uses-sdk')
indent = get_indent(manifest.firstChild, 1)
manifest.insertBefore(element, manifest.firstChild)
# Insert an indent before uses-sdk to line it up with the indentation of the
# other children of the <manifest> tag.
manifest.insertBefore(doc.createTextNode(indent), manifest.firstChild)
# Get or insert the minSdkVersion attribute. If it is already present, make
# sure it as least the requested value.
min_attr = element.getAttributeNodeNS(android_ns, 'minSdkVersion')
if min_attr is None:
min_attr = doc.createAttributeNS(android_ns, 'android:minSdkVersion')
min_attr.value = min_sdk_version
element.setAttributeNode(min_attr)
else:
if compare_version_gt(min_sdk_version, min_attr.value):
min_attr.value = min_sdk_version
# Insert the targetSdkVersion attribute if it is missing. If it is already
# present leave it as is.
target_attr = element.getAttributeNodeNS(android_ns, 'targetSdkVersion')
if target_attr is None:
target_attr = doc.createAttributeNS(android_ns, 'android:targetSdkVersion')
if library:
# TODO(b/117122200): libraries shouldn't set targetSdkVersion at all, but
# ManifestMerger treats minSdkVersion="Q" as targetSdkVersion="Q" if it
# is empty. Set it to something low so that it will be overriden by the
# main manifest, but high enough that it doesn't cause implicit
# permissions grants.
target_attr.value = '15'
else:
target_attr.value = target_sdk_version
element.setAttributeNode(target_attr)
def add_uses_libraries(doc, new_uses_libraries, required):
"""Add additional <uses-library> tags
Args:
doc: The XML document. May be modified by this function.
new_uses_libraries: The names of libraries to be added by this function.
required: The value of android:required attribute. Can be true or false.
Raises:
RuntimeError: Invalid manifest
"""
manifest = parse_manifest(doc)
elems = get_children_with_tag(manifest, 'application')
application = elems[0] if len(elems) == 1 else None
if len(elems) > 1:
raise RuntimeError('found multiple <application> tags')
elif not elems:
application = doc.createElement('application')
indent = get_indent(manifest.firstChild, 1)
first = manifest.firstChild
manifest.insertBefore(doc.createTextNode(indent), first)
manifest.insertBefore(application, first)
indent = get_indent(application.firstChild, 2)
last = application.lastChild
if last is not None and last.nodeType != minidom.Node.TEXT_NODE:
last = None
for name in new_uses_libraries:
if find_child_with_attribute(application, 'uses-library', android_ns,
'name', name) is not None:
# If the uses-library tag of the same 'name' attribute value exists,
# respect it.
continue
ul = doc.createElement('uses-library')
ul.setAttributeNS(android_ns, 'android:name', name)
ul.setAttributeNS(android_ns, 'android:required', str(required).lower())
application.insertBefore(doc.createTextNode(indent), last)
application.insertBefore(ul, last)
# align the closing tag with the opening tag if it's not
# indented
if application.lastChild.nodeType != minidom.Node.TEXT_NODE:
indent = get_indent(application.previousSibling, 1)
application.appendChild(doc.createTextNode(indent))
def add_uses_non_sdk_api(doc):
"""Add android:usesNonSdkApi=true attribute to <application>.
Args:
doc: The XML document. May be modified by this function.
Raises:
RuntimeError: Invalid manifest
"""
manifest = parse_manifest(doc)
elems = get_children_with_tag(manifest, 'application')
application = elems[0] if len(elems) == 1 else None
if len(elems) > 1:
raise RuntimeError('found multiple <application> tags')
elif not elems:
application = doc.createElement('application')
indent = get_indent(manifest.firstChild, 1)
first = manifest.firstChild
manifest.insertBefore(doc.createTextNode(indent), first)
manifest.insertBefore(application, first)
attr = application.getAttributeNodeNS(android_ns, 'usesNonSdkApi')
if attr is None:
attr = doc.createAttributeNS(android_ns, 'android:usesNonSdkApi')
attr.value = 'true'
application.setAttributeNode(attr)
def write_xml(f, doc):
f.write('<?xml version="1.0" encoding="utf-8"?>\n')
for node in doc.childNodes:
f.write(node.toxml(encoding='utf-8') + '\n')
def main():
"""Program entry point."""
try:
args = parse_args()
doc = minidom.parse(args.input)
ensure_manifest_android_ns(doc)
if args.raise_min_sdk_version:
raise_min_sdk_version(doc, args.min_sdk_version, args.target_sdk_version, args.library)
if args.uses_libraries:
add_uses_libraries(doc, args.uses_libraries, True)
if args.optional_uses_libraries:
add_uses_libraries(doc, args.optional_uses_libraries, False)
if args.uses_non_sdk_api:
add_uses_non_sdk_api(doc)
with open(args.output, 'wb') as f:
write_xml(f, doc)
# pylint: disable=broad-except
except Exception as err:
print('error: ' + str(err), file=sys.stderr)
sys.exit(-1)
if __name__ == '__main__':
main()