blob: efa85ecb36edd94d7f2c3c9d6ff33a6391aad36c [file] [log] [blame]
Paul Duffin50243ae2025-03-03 19:10:27 +00001#!/usr/bin/env python3
2
3# Copyright (C) 2025 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"""Converts a set of aconfig protobuf flags to a Metalava config file."""
18
19# Formatted using `pyformat -i scripts/aconfig-to-metalava-flags.py`
20
21import argparse
22import sys
23import xml.etree.ElementTree as ET
24
25from protos import aconfig_pb2
26
27_READ_ONLY = aconfig_pb2.flag_permission.READ_ONLY
28_ENABLED = aconfig_pb2.flag_state.ENABLED
29_DISABLED = aconfig_pb2.flag_state.DISABLED
30
31# The namespace of the Metalava config file.
32CONFIG_NS = 'http://www.google.com/tools/metalava/config'
33
34
35def config_name(tag: str):
36 """Create a QName in the config namespace.
37
38 :param:tag the name of the entity in the config namespace.
39 """
40 return f'{{{CONFIG_NS}}}{tag}'
41
42
43def main():
44 """Program entry point."""
45 args_parser = argparse.ArgumentParser(
46 description='Generate Metalava flags config from aconfig protobuf',
47 )
48 args_parser.add_argument(
49 'input',
50 help='The path to the aconfig protobuf file',
51 )
52 args = args_parser.parse_args(sys.argv[1:])
53
54 # Read the parsed_flags from the protobuf file.
55 with open(args.input, 'rb') as f:
56 parsed_flags = aconfig_pb2.parsed_flags.FromString(f.read())
57
58 # Create the structure of the XML config file.
59 config = ET.Element(config_name('config'))
60 api_flags = ET.SubElement(config, config_name('api-flags'))
61 # Create an <api-flag> element for each parsed_flag.
62 for flag in parsed_flags.parsed_flag:
63 if flag.permission == _READ_ONLY:
64 # Ignore any read only disabled flags as Metalava assumes that as the
65 # default when an <api-flags/> element is provided so this reduces the
66 # size of the file.
67 if flag.state == _DISABLED:
68 continue
69 mutability = 'immutable'
70 else:
71 mutability = 'mutable'
72 if flag.state == _ENABLED:
73 status = 'enabled'
74 else:
75 status = 'disabled'
76 attributes = {
77 'package': flag.package,
78 'name': flag.name,
79 'mutability': mutability,
80 'status': status,
81 }
82 # Convert the attribute names into qualified names in, what will become, the
83 # default namespace for the XML file. This is needed to ensure that the
84 # attribute will be written in the XML file without a prefix, e.g.
85 # `name="flag_name"`. Without it, a namespace prefix, e.g. `ns1`, will be
86 # synthesized for the attribute when writing the XML file, i.e. it
87 # will be written as `ns1:name="flag_name"`. Strictly speaking, that is
88 # unnecessary as the "Namespaces in XML 1.0 (Third Edition)" specification
89 # says that unprefixed attribute names have no namespace.
90 qualified_attributes = {config_name(k): v for (k, v) in attributes.items()}
91 ET.SubElement(api_flags, config_name('api-flag'), qualified_attributes)
92
93 # Create a tree and add white space so it will pretty print when written out.
94 tree = ET.ElementTree(config)
95 ET.indent(tree)
96
97 # Write the tree using the config namespace as the default.
98 tree.write(sys.stdout, encoding='unicode', default_namespace=CONFIG_NS)
99 sys.stdout.close()
100
101
102if __name__ == '__main__':
103 main()