blob: f34f6c31314df4a05a608c1b70443440c1ec73b5 [file] [log] [blame]
Colin Cross8bb10e82018-06-07 16:46:02 -07001#!/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
20import argparse
21import sys
22from xml.dom import minidom
23
24
25android_ns = 'http://schemas.android.com/apk/res/android'
26
27
28def get_children_with_tag(parent, tag_name):
29 children = []
30 for child in parent.childNodes:
31 if child.nodeType == minidom.Node.ELEMENT_NODE and \
32 child.tagName == tag_name:
33 children.append(child)
34 return children
35
36
37def parse_args():
38 """Parse commandline arguments."""
39
40 parser = argparse.ArgumentParser()
41 parser.add_argument('--minSdkVersion', default='', dest='min_sdk_version',
42 help='specify minSdkVersion used by the build system')
43 parser.add_argument('input', help='input AndroidManifest.xml file')
44 parser.add_argument('output', help='input AndroidManifest.xml file')
45 return parser.parse_args()
46
47
48def parse_manifest(doc):
49 """Get the manifest element."""
50
51 manifest = doc.documentElement
52 if manifest.tagName != 'manifest':
53 raise RuntimeError('expected manifest tag at root')
54 return manifest
55
56
57def ensure_manifest_android_ns(doc):
58 """Make sure the manifest tag defines the android namespace."""
59
60 manifest = parse_manifest(doc)
61
62 ns = manifest.getAttributeNodeNS(minidom.XMLNS_NAMESPACE, 'android')
63 if ns is None:
64 attr = doc.createAttributeNS(minidom.XMLNS_NAMESPACE, 'xmlns:android')
65 attr.value = android_ns
66 manifest.setAttributeNode(attr)
67 elif ns.value != android_ns:
68 raise RuntimeError('manifest tag has incorrect android namespace ' +
69 ns.value)
70
71
72def as_int(s):
73 try:
74 i = int(s)
75 except ValueError:
76 return s, False
77 return i, True
78
79
80def compare_version_gt(a, b):
81 """Compare two SDK versions.
82
83 Compares a and b, treating codenames like 'Q' as higher
84 than numerical versions like '28'.
85
86 Returns True if a > b
87
88 Args:
89 a: value to compare
90 b: value to compare
91 Returns:
92 True if a is a higher version than b
93 """
94
95 a, a_is_int = as_int(a.upper())
96 b, b_is_int = as_int(b.upper())
97
98 if a_is_int == b_is_int:
99 # Both are codenames or both are versions, compare directly
100 return a > b
101 else:
102 # One is a codename, the other is not. Return true if
103 # b is an integer version
104 return b_is_int
105
106
107def raise_min_sdk_version(doc, requested):
108 """Ensure the manifest contains a <uses-sdk> tag with a minSdkVersion.
109
110 Args:
111 doc: The XML document. May be modified by this function.
112 requested: The requested minSdkVersion attribute.
113 Raises:
114 RuntimeError: invalid manifest
115 """
116
117 manifest = parse_manifest(doc)
118
119 # Get or insert the uses-sdk element
120 uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
121 if len(uses_sdk) > 1:
122 raise RuntimeError('found multiple uses-sdk elements')
123 elif len(uses_sdk) == 1:
124 element = uses_sdk[0]
125 else:
126 element = doc.createElement('uses-sdk')
127 indent = ''
128 first = manifest.firstChild
129 if first is not None and first.nodeType == minidom.Node.TEXT_NODE:
130 text = first.nodeValue
131 indent = text[:len(text)-len(text.lstrip())]
132 if not indent or indent == '\n':
133 indent = '\n '
134
135 manifest.insertBefore(element, manifest.firstChild)
136
137 # Insert an indent before uses-sdk to line it up with the indentation of the
138 # other children of the <manifest> tag.
139 manifest.insertBefore(doc.createTextNode(indent), manifest.firstChild)
140
141 # Get or insert the minSdkVersion attribute
142 min_attr = element.getAttributeNodeNS(android_ns, 'minSdkVersion')
143 if min_attr is None:
144 min_attr = doc.createAttributeNS(android_ns, 'android:minSdkVersion')
145 min_attr.value = '1'
146 element.setAttributeNode(min_attr)
147
148 # Update the value of the minSdkVersion attribute if necessary
149 if compare_version_gt(requested, min_attr.value):
150 min_attr.value = requested
151
152
153def write_xml(f, doc):
154 f.write('<?xml version="1.0" encoding="utf-8"?>\n')
155 for node in doc.childNodes:
156 f.write(node.toxml(encoding='utf-8') + '\n')
157
158
159def main():
160 """Program entry point."""
161 try:
162 args = parse_args()
163
164 doc = minidom.parse(args.input)
165
166 ensure_manifest_android_ns(doc)
167
168 if args.min_sdk_version:
169 raise_min_sdk_version(doc, args.min_sdk_version)
170
171 with open(args.output, 'wb') as f:
172 write_xml(f, doc)
173
174 # pylint: disable=broad-except
175 except Exception as err:
176 print('error: ' + str(err), file=sys.stderr)
177 sys.exit(-1)
178
179if __name__ == '__main__':
180 main()