xref: /aosp_15_r20/build/soong/scripts/manifest.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1#!/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 or a test config."""
18
19from xml.dom import minidom
20
21
22android_ns = 'http://schemas.android.com/apk/res/android'
23
24
25def get_or_create_applications(doc, manifest):
26  """Get all <application> tags from the manifest, or create one if none exist.
27  Multiple <application> tags may exist when manifest feature flagging is used.
28  """
29  applications = get_children_with_tag(manifest, 'application')
30  if len(applications) == 0:
31    application = doc.createElement('application')
32    indent = get_indent(manifest.firstChild, 1)
33    first = manifest.firstChild
34    manifest.insertBefore(doc.createTextNode(indent), first)
35    manifest.insertBefore(application, first)
36    applications.append(application)
37  return applications
38
39
40def get_or_create_uses_sdks(doc, manifest):
41  """Get all <uses-sdk> tags from the manifest, or create one if none exist.
42  Multiple <uses-sdk> tags may exist when manifest feature flagging is used.
43  """
44  uses_sdks = get_children_with_tag(manifest, 'uses-sdk')
45  if len(uses_sdks) == 0:
46    uses_sdk = doc.createElement('uses-sdk')
47    indent = get_indent(manifest.firstChild, 1)
48    manifest.insertBefore(uses_sdk, manifest.firstChild)
49
50    # Insert an indent before uses-sdk to line it up with the indentation of the
51    # other children of the <manifest> tag.
52    manifest.insertBefore(doc.createTextNode(indent), manifest.firstChild)
53    uses_sdks.append(uses_sdk)
54  return uses_sdks
55
56def get_children_with_tag(parent, tag_name):
57  children = []
58  for child in parent.childNodes:
59    if child.nodeType == minidom.Node.ELEMENT_NODE and \
60       child.tagName == tag_name:
61      children.append(child)
62  return children
63
64
65def find_child_with_attribute(element, tag_name, namespace_uri,
66                              attr_name, value):
67  for child in get_children_with_tag(element, tag_name):
68    attr = child.getAttributeNodeNS(namespace_uri, attr_name)
69    if attr is not None and attr.value == value:
70      return child
71  return None
72
73
74def parse_manifest(doc):
75  """Get the manifest element."""
76
77  manifest = doc.documentElement
78  if manifest.tagName != 'manifest':
79    raise RuntimeError('expected manifest tag at root')
80  return manifest
81
82
83def ensure_manifest_android_ns(doc):
84  """Make sure the manifest tag defines the android namespace."""
85
86  manifest = parse_manifest(doc)
87
88  ns = manifest.getAttributeNodeNS(minidom.XMLNS_NAMESPACE, 'android')
89  if ns is None:
90    attr = doc.createAttributeNS(minidom.XMLNS_NAMESPACE, 'xmlns:android')
91    attr.value = android_ns
92    manifest.setAttributeNode(attr)
93  elif ns.value != android_ns:
94    raise RuntimeError('manifest tag has incorrect android namespace ' +
95                       ns.value)
96
97
98def parse_test_config(doc):
99  """ Get the configuration element. """
100
101  test_config = doc.documentElement
102  if test_config.tagName != 'configuration':
103    raise RuntimeError('expected configuration tag at root')
104  return test_config
105
106
107def as_int(s):
108  try:
109    i = int(s)
110  except ValueError:
111    return s, False
112  return i, True
113
114
115def compare_version_gt(a, b):
116  """Compare two SDK versions.
117
118  Compares a and b, treating codenames like 'Q' as higher
119  than numerical versions like '28'.
120
121  Returns True if a > b
122
123  Args:
124    a: value to compare
125    b: value to compare
126  Returns:
127    True if a is a higher version than b
128  """
129
130  a, a_is_int = as_int(a.upper())
131  b, b_is_int = as_int(b.upper())
132
133  if a_is_int == b_is_int:
134    # Both are codenames or both are versions, compare directly
135    return a > b
136  else:
137    # One is a codename, the other is not.  Return true if
138    # b is an integer version
139    return b_is_int
140
141
142def get_indent(element, default_level):
143  indent = ''
144  if element is not None and element.nodeType == minidom.Node.TEXT_NODE:
145    text = element.nodeValue
146    indent = text[:len(text)-len(text.lstrip())]
147  if not indent or indent == '\n':
148    # 1 indent = 4 space
149    indent = '\n' + (' ' * default_level * 4)
150  return indent
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() + '\n')
157