diff options
Diffstat (limited to 'scripts/aconfig-to-metalava-flags.py')
-rw-r--r-- | scripts/aconfig-to-metalava-flags.py | 103 |
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() |