summaryrefslogtreecommitdiff
path: root/scripts/aconfig-to-metalava-flags.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/aconfig-to-metalava-flags.py')
-rw-r--r--scripts/aconfig-to-metalava-flags.py103
1 files changed, 103 insertions, 0 deletions
diff --git a/scripts/aconfig-to-metalava-flags.py b/scripts/aconfig-to-metalava-flags.py
new file mode 100644
index 000000000..efa85ecb3
--- /dev/null
+++ b/scripts/aconfig-to-metalava-flags.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2025 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.
+
+"""Converts a set of aconfig protobuf flags to a Metalava config file."""
+
+# Formatted using `pyformat -i scripts/aconfig-to-metalava-flags.py`
+
+import argparse
+import sys
+import xml.etree.ElementTree as ET
+
+from protos import aconfig_pb2
+
+_READ_ONLY = aconfig_pb2.flag_permission.READ_ONLY
+_ENABLED = aconfig_pb2.flag_state.ENABLED
+_DISABLED = aconfig_pb2.flag_state.DISABLED
+
+# The namespace of the Metalava config file.
+CONFIG_NS = 'http://www.google.com/tools/metalava/config'
+
+
+def config_name(tag: str):
+ """Create a QName in the config namespace.
+
+ :param:tag the name of the entity in the config namespace.
+ """
+ return f'{{{CONFIG_NS}}}{tag}'
+
+
+def main():
+ """Program entry point."""
+ args_parser = argparse.ArgumentParser(
+ description='Generate Metalava flags config from aconfig protobuf',
+ )
+ args_parser.add_argument(
+ 'input',
+ help='The path to the aconfig protobuf file',
+ )
+ args = args_parser.parse_args(sys.argv[1:])
+
+ # Read the parsed_flags from the protobuf file.
+ with open(args.input, 'rb') as f:
+ parsed_flags = aconfig_pb2.parsed_flags.FromString(f.read())
+
+ # Create the structure of the XML config file.
+ config = ET.Element(config_name('config'))
+ api_flags = ET.SubElement(config, config_name('api-flags'))
+ # Create an <api-flag> element for each parsed_flag.
+ for flag in parsed_flags.parsed_flag:
+ if flag.permission == _READ_ONLY:
+ # Ignore any read only disabled flags as Metalava assumes that as the
+ # default when an <api-flags/> element is provided so this reduces the
+ # size of the file.
+ if flag.state == _DISABLED:
+ continue
+ mutability = 'immutable'
+ else:
+ mutability = 'mutable'
+ if flag.state == _ENABLED:
+ status = 'enabled'
+ else:
+ status = 'disabled'
+ attributes = {
+ 'package': flag.package,
+ 'name': flag.name,
+ 'mutability': mutability,
+ 'status': status,
+ }
+ # Convert the attribute names into qualified names in, what will become, the
+ # default namespace for the XML file. This is needed to ensure that the
+ # attribute will be written in the XML file without a prefix, e.g.
+ # `name="flag_name"`. Without it, a namespace prefix, e.g. `ns1`, will be
+ # synthesized for the attribute when writing the XML file, i.e. it
+ # will be written as `ns1:name="flag_name"`. Strictly speaking, that is
+ # unnecessary as the "Namespaces in XML 1.0 (Third Edition)" specification
+ # says that unprefixed attribute names have no namespace.
+ qualified_attributes = {config_name(k): v for (k, v) in attributes.items()}
+ ET.SubElement(api_flags, config_name('api-flag'), qualified_attributes)
+
+ # Create a tree and add white space so it will pretty print when written out.
+ tree = ET.ElementTree(config)
+ ET.indent(tree)
+
+ # Write the tree using the config namespace as the default.
+ tree.write(sys.stdout, encoding='unicode', default_namespace=CONFIG_NS)
+ sys.stdout.close()
+
+
+if __name__ == '__main__':
+ main()