aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2018-06-07 16:46:02 -0700
committerColin Cross <ccross@android.com>2018-06-14 14:54:27 -0700
commit8bb10e8f8a72e6f6e87e9812dacb3816a37c7ed5 (patch)
tree570aa4b252af44fb85ce7131be5895a6bc2f790b /scripts
parent30485c920c64880856c58bc33e2b45109a13c004 (diff)
downloadbuild_soong-8bb10e8f8a72e6f6e87e9812dacb3816a37c7ed5.tar.gz
build_soong-8bb10e8f8a72e6f6e87e9812dacb3816a37c7ed5.tar.bz2
build_soong-8bb10e8f8a72e6f6e87e9812dacb3816a37c7ed5.zip
Add a script to inject values into manifests
Add a script that can inject a <uses-sdk minSdkVersion=""> into AndroidManifest.xml files. This will help with merging LOCAL_STATIC_ANDROID_LIBRARIES, because ManifestMerger treats a missing minSdkVersion as minSdkVersion=1 and throws errors if libraries use a larger minSdkVersion. It will also help with cases where an app has a manifest that specifies an old minSdkVersion, but the build system is compiling the app in a way that is not compatibile with old devices, for example using a newer dex format. Bug: 110167203 Test: m java Test: build/soong/scripts/manifest_fixer_test.py Change-Id: I528d71a225feb86464c530e11b223babb0ea9edf
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/manifest_fixer.py180
-rwxr-xr-xscripts/manifest_fixer_test.py162
2 files changed, 342 insertions, 0 deletions
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
new file mode 100755
index 00000000..f34f6c31
--- /dev/null
+++ b/scripts/manifest_fixer.py
@@ -0,0 +1,180 @@
+#!/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 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('input', help='input AndroidManifest.xml file')
+ parser.add_argument('output', help='input 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 raise_min_sdk_version(doc, requested):
+ """Ensure the manifest contains a <uses-sdk> tag with a minSdkVersion.
+
+ Args:
+ doc: The XML document. May be modified by this function.
+ requested: The requested minSdkVersion 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 = ''
+ first = manifest.firstChild
+ if first is not None and first.nodeType == minidom.Node.TEXT_NODE:
+ text = first.nodeValue
+ indent = text[:len(text)-len(text.lstrip())]
+ if not indent or indent == '\n':
+ indent = '\n '
+
+ 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
+ min_attr = element.getAttributeNodeNS(android_ns, 'minSdkVersion')
+ if min_attr is None:
+ min_attr = doc.createAttributeNS(android_ns, 'android:minSdkVersion')
+ min_attr.value = '1'
+ element.setAttributeNode(min_attr)
+
+ # Update the value of the minSdkVersion attribute if necessary
+ if compare_version_gt(requested, min_attr.value):
+ min_attr.value = requested
+
+
+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.min_sdk_version:
+ raise_min_sdk_version(doc, args.min_sdk_version)
+
+ 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()
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
new file mode 100755
index 00000000..ccfa8fbe
--- /dev/null
+++ b/scripts/manifest_fixer_test.py
@@ -0,0 +1,162 @@
+#!/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.
+#
+"""Unit tests for manifest_fixer_test.py."""
+
+import StringIO
+import sys
+import unittest
+from xml.dom import minidom
+
+import manifest_fixer
+
+sys.dont_write_bytecode = True
+
+
+class CompareVersionGtTest(unittest.TestCase):
+ """Unit tests for compare_version_gt function."""
+
+ def test_sdk(self):
+ """Test comparing sdk versions."""
+ self.assertTrue(manifest_fixer.compare_version_gt('28', '27'))
+ self.assertFalse(manifest_fixer.compare_version_gt('27', '28'))
+ self.assertFalse(manifest_fixer.compare_version_gt('28', '28'))
+
+ def test_codename(self):
+ """Test comparing codenames."""
+ self.assertTrue(manifest_fixer.compare_version_gt('Q', 'P'))
+ self.assertFalse(manifest_fixer.compare_version_gt('P', 'Q'))
+ self.assertFalse(manifest_fixer.compare_version_gt('Q', 'Q'))
+
+ def test_sdk_codename(self):
+ """Test comparing sdk versions with codenames."""
+ self.assertTrue(manifest_fixer.compare_version_gt('Q', '28'))
+ self.assertFalse(manifest_fixer.compare_version_gt('28', 'Q'))
+
+ def test_compare_numeric(self):
+ """Test that numbers are compared in numeric and not lexicographic order."""
+ self.assertTrue(manifest_fixer.compare_version_gt('18', '8'))
+
+
+class RaiseMinSdkVersionTest(unittest.TestCase):
+ """Unit tests for raise_min_sdk_version function."""
+
+ def raise_min_sdk_version_test(self, input_manifest, min_sdk_version):
+ doc = minidom.parseString(input_manifest)
+ manifest_fixer.raise_min_sdk_version(doc, min_sdk_version)
+ output = StringIO.StringIO()
+ manifest_fixer.write_xml(output, doc)
+ return output.getvalue()
+
+ manifest_tmpl = (
+ '<?xml version="1.0" encoding="utf-8"?>\n'
+ '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+ '%s'
+ '</manifest>\n')
+
+ def uses_sdk(self, v, extra=''):
+ if extra:
+ extra = ' ' + extra
+ return ' <uses-sdk android:minSdkVersion="%s"%s/>\n' % (v, extra)
+
+ def test_no_uses_sdk(self):
+ """Tests inserting a uses-sdk element into a manifest."""
+
+ manifest_input = self.manifest_tmpl % ''
+ expected = self.manifest_tmpl % self.uses_sdk('28')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_no_min(self):
+ """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
+
+ manifest_input = self.manifest_tmpl % ' <uses-sdk extra="foo"/>\n'
+ expected = self.manifest_tmpl % self.uses_sdk('28', 'extra="foo"')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_raise_min(self):
+ """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('27')
+ expected = self.manifest_tmpl % self.uses_sdk('28')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_raise(self):
+ """Tests raising a minSdkVersion attribute."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('27')
+ expected = self.manifest_tmpl % self.uses_sdk('28')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_no_raise_min(self):
+ """Tests a minSdkVersion that doesn't need raising."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('28')
+ expected = manifest_input
+ output = self.raise_min_sdk_version_test(manifest_input, '27')
+ self.assertEqual(output, expected)
+
+ def test_raise_codename(self):
+ """Tests raising a minSdkVersion attribute to a codename."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('28')
+ expected = self.manifest_tmpl % self.uses_sdk('P')
+ output = self.raise_min_sdk_version_test(manifest_input, 'P')
+ self.assertEqual(output, expected)
+
+ def test_no_raise_codename(self):
+ """Tests a minSdkVersion codename that doesn't need raising."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('P')
+ expected = manifest_input
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_extra(self):
+ """Tests that extra attributes and elements are maintained."""
+
+ manifest_input = self.manifest_tmpl % (
+ ' <!-- comment -->\n'
+ ' <uses-sdk android:minSdkVersion="27" extra="foo"/>\n'
+ ' <application/>\n')
+
+ expected = self.manifest_tmpl % (
+ ' <!-- comment -->\n'
+ ' <uses-sdk android:minSdkVersion="28" extra="foo"/>\n'
+ ' <application/>\n')
+
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+
+ self.assertEqual(output, expected)
+
+ def test_indent(self):
+ """Tests that an inserted element copies the existing indentation."""
+
+ manifest_input = self.manifest_tmpl % ' <!-- comment -->\n'
+
+ expected = self.manifest_tmpl % (
+ ' <uses-sdk android:minSdkVersion="28"/>\n'
+ ' <!-- comment -->\n')
+
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+
+ self.assertEqual(output, expected)
+
+if __name__ == '__main__':
+ unittest.main()