xref: /aosp_15_r20/system/chre/api_parser/api_parser.py (revision 84e339476a462649f82315436d70fd732297a399)
1*84e33947SAndroid Build Coastguard Worker#!/usr/bin/python3
2*84e33947SAndroid Build Coastguard Worker#
3*84e33947SAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project
4*84e33947SAndroid Build Coastguard Worker#
5*84e33947SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*84e33947SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*84e33947SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*84e33947SAndroid Build Coastguard Worker#
9*84e33947SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*84e33947SAndroid Build Coastguard Worker#
11*84e33947SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*84e33947SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*84e33947SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*84e33947SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*84e33947SAndroid Build Coastguard Worker# limitations under the License.
16*84e33947SAndroid Build Coastguard Worker#
17*84e33947SAndroid Build Coastguard Worker
18*84e33947SAndroid Build Coastguard Workerimport os
19*84e33947SAndroid Build Coastguard Worker
20*84e33947SAndroid Build Coastguard Workerfrom collections import defaultdict
21*84e33947SAndroid Build Coastguard Workerfrom pyclibrary import CParser
22*84e33947SAndroid Build Coastguard Worker
23*84e33947SAndroid Build Coastguard Workerfrom utils import system_chre_abs_path
24*84e33947SAndroid Build Coastguard Worker
25*84e33947SAndroid Build Coastguard Worker
26*84e33947SAndroid Build Coastguard Workerclass ApiParser:
27*84e33947SAndroid Build Coastguard Worker    """Given a file-specific set of annotations (extracted from JSON annotations file), parses a
28*84e33947SAndroid Build Coastguard Worker    single API header file into data structures suitable for use with code generation. This class
29*84e33947SAndroid Build Coastguard Worker    will contain the parsed representation of the headers when instantiated.
30*84e33947SAndroid Build Coastguard Worker    """
31*84e33947SAndroid Build Coastguard Worker
32*84e33947SAndroid Build Coastguard Worker    def __init__(self, json_obj):
33*84e33947SAndroid Build Coastguard Worker        """Initialize and parse the API file described in the provided JSON-derived object.
34*84e33947SAndroid Build Coastguard Worker
35*84e33947SAndroid Build Coastguard Worker        :param json_obj: Extracted file-specific annotations from JSON
36*84e33947SAndroid Build Coastguard Worker        """
37*84e33947SAndroid Build Coastguard Worker
38*84e33947SAndroid Build Coastguard Worker        self.json = json_obj
39*84e33947SAndroid Build Coastguard Worker        self.structs_and_unions = {}
40*84e33947SAndroid Build Coastguard Worker        self._parse_annotations()
41*84e33947SAndroid Build Coastguard Worker        self._parse_api()
42*84e33947SAndroid Build Coastguard Worker
43*84e33947SAndroid Build Coastguard Worker    def _parse_annotations(self):
44*84e33947SAndroid Build Coastguard Worker        # Converts annotations list to a more usable data structure: dict keyed by structure name,
45*84e33947SAndroid Build Coastguard Worker        # containing a dict keyed by field name, containing a list of annotations (as they
46*84e33947SAndroid Build Coastguard Worker        # appear in the JSON). In other words, we can easily get all of the annotations for the
47*84e33947SAndroid Build Coastguard Worker        # "version" field in "chreWwanCellInfoResult" via
48*84e33947SAndroid Build Coastguard Worker        # annotations['chreWwanCellInfoResult']['version']. This is also a defaultdict, so it's safe
49*84e33947SAndroid Build Coastguard Worker        # to access if there are no annotations for this structure + field; it'll just give you
50*84e33947SAndroid Build Coastguard Worker        # an empty list in that case.
51*84e33947SAndroid Build Coastguard Worker
52*84e33947SAndroid Build Coastguard Worker        self.annotations = defaultdict(lambda: defaultdict(list))
53*84e33947SAndroid Build Coastguard Worker        for struct_info in self.json['struct_info']:
54*84e33947SAndroid Build Coastguard Worker            for annotation in struct_info['annotations']:
55*84e33947SAndroid Build Coastguard Worker                self.annotations[struct_info['name']
56*84e33947SAndroid Build Coastguard Worker                                 ][annotation['field']].append(annotation)
57*84e33947SAndroid Build Coastguard Worker
58*84e33947SAndroid Build Coastguard Worker    def _files_to_parse(self):
59*84e33947SAndroid Build Coastguard Worker        """Returns a list of files to supply as input to CParser"""
60*84e33947SAndroid Build Coastguard Worker
61*84e33947SAndroid Build Coastguard Worker        # Input paths for CParser are stored in JSON relative to <android_root>/system/chre
62*84e33947SAndroid Build Coastguard Worker        # Reformulate these to absolute paths, and add in some default includes that we always
63*84e33947SAndroid Build Coastguard Worker        # supply
64*84e33947SAndroid Build Coastguard Worker        chre_project_base_dir = system_chre_abs_path()
65*84e33947SAndroid Build Coastguard Worker        default_includes = ['api_parser/parser_defines.h',
66*84e33947SAndroid Build Coastguard Worker                            'chre_api/include/chre_api/chre/version.h']
67*84e33947SAndroid Build Coastguard Worker        files = default_includes + \
68*84e33947SAndroid Build Coastguard Worker            self.json['includes'] + [self.json['filename']]
69*84e33947SAndroid Build Coastguard Worker        return [os.path.join(chre_project_base_dir, file) for file in files]
70*84e33947SAndroid Build Coastguard Worker
71*84e33947SAndroid Build Coastguard Worker    def _parse_structs_and_unions(self):
72*84e33947SAndroid Build Coastguard Worker        # Starts with the root structures (i.e. those that will appear at the top-level in one
73*84e33947SAndroid Build Coastguard Worker        # or more CHPP messages), build a data structure containing all of the information we'll
74*84e33947SAndroid Build Coastguard Worker        # need to emit the CHPP structure definition and conversion code.
75*84e33947SAndroid Build Coastguard Worker
76*84e33947SAndroid Build Coastguard Worker        structs_and_unions_to_parse = self.json['root_structs'].copy()
77*84e33947SAndroid Build Coastguard Worker        while len(structs_and_unions_to_parse) > 0:
78*84e33947SAndroid Build Coastguard Worker            type_name = structs_and_unions_to_parse.pop()
79*84e33947SAndroid Build Coastguard Worker            if type_name in self.structs_and_unions:
80*84e33947SAndroid Build Coastguard Worker                continue
81*84e33947SAndroid Build Coastguard Worker
82*84e33947SAndroid Build Coastguard Worker            entry = {
83*84e33947SAndroid Build Coastguard Worker                'appears_in': set(),  # Other types this type is nested within
84*84e33947SAndroid Build Coastguard Worker                'dependencies': set(),  # Types that are nested in this type
85*84e33947SAndroid Build Coastguard Worker                'has_vla_member': False,  # True if this type or any dependency has a VLA member
86*84e33947SAndroid Build Coastguard Worker                'members': [],  # Info about each member of this type
87*84e33947SAndroid Build Coastguard Worker            }
88*84e33947SAndroid Build Coastguard Worker            if type_name in self.parser.defs['structs']:
89*84e33947SAndroid Build Coastguard Worker                defs = self.parser.defs['structs'][type_name]
90*84e33947SAndroid Build Coastguard Worker                entry['is_union'] = False
91*84e33947SAndroid Build Coastguard Worker            elif type_name in self.parser.defs['unions']:
92*84e33947SAndroid Build Coastguard Worker                defs = self.parser.defs['unions'][type_name]
93*84e33947SAndroid Build Coastguard Worker                entry['is_union'] = True
94*84e33947SAndroid Build Coastguard Worker            else:
95*84e33947SAndroid Build Coastguard Worker                raise RuntimeError(
96*84e33947SAndroid Build Coastguard Worker                    "Couldn't find {} in parsed structs/unions".format(type_name))
97*84e33947SAndroid Build Coastguard Worker
98*84e33947SAndroid Build Coastguard Worker            for member_name, member_type, _ in defs['members']:
99*84e33947SAndroid Build Coastguard Worker                member_info = {
100*84e33947SAndroid Build Coastguard Worker                    'name': member_name,
101*84e33947SAndroid Build Coastguard Worker                    'type': member_type,
102*84e33947SAndroid Build Coastguard Worker                    'annotations': self.annotations[type_name][member_name],
103*84e33947SAndroid Build Coastguard Worker                    'is_nested_type': False,
104*84e33947SAndroid Build Coastguard Worker                }
105*84e33947SAndroid Build Coastguard Worker
106*84e33947SAndroid Build Coastguard Worker                if member_type.type_spec.startswith('struct ') or \
107*84e33947SAndroid Build Coastguard Worker                        member_type.type_spec.startswith('union '):
108*84e33947SAndroid Build Coastguard Worker                    member_info['is_nested_type'] = True
109*84e33947SAndroid Build Coastguard Worker                    member_type_name = member_type.type_spec.split(' ')[1]
110*84e33947SAndroid Build Coastguard Worker                    member_info['nested_type_name'] = member_type_name
111*84e33947SAndroid Build Coastguard Worker                    entry['dependencies'].add(member_type_name)
112*84e33947SAndroid Build Coastguard Worker                    structs_and_unions_to_parse.append(member_type_name)
113*84e33947SAndroid Build Coastguard Worker
114*84e33947SAndroid Build Coastguard Worker                entry['members'].append(member_info)
115*84e33947SAndroid Build Coastguard Worker
116*84e33947SAndroid Build Coastguard Worker                # Flip a flag if this structure has at least one variable-length array member, which
117*84e33947SAndroid Build Coastguard Worker                # means that the encoded size can only be computed at runtime
118*84e33947SAndroid Build Coastguard Worker                if not entry['has_vla_member']:
119*84e33947SAndroid Build Coastguard Worker                    for annotation in self.annotations[type_name][member_name]:
120*84e33947SAndroid Build Coastguard Worker                        if annotation['annotation'] == 'var_len_array':
121*84e33947SAndroid Build Coastguard Worker                            entry['has_vla_member'] = True
122*84e33947SAndroid Build Coastguard Worker
123*84e33947SAndroid Build Coastguard Worker            self.structs_and_unions[type_name] = entry
124*84e33947SAndroid Build Coastguard Worker
125*84e33947SAndroid Build Coastguard Worker        # Build reverse linkage of dependency chain (i.e. lookup between a type and the other types
126*84e33947SAndroid Build Coastguard Worker        # it appears in)
127*84e33947SAndroid Build Coastguard Worker        for type_name, type_info in self.structs_and_unions.items():
128*84e33947SAndroid Build Coastguard Worker            for dependency in type_info['dependencies']:
129*84e33947SAndroid Build Coastguard Worker                self.structs_and_unions[dependency]['appears_in'].add(
130*84e33947SAndroid Build Coastguard Worker                    type_name)
131*84e33947SAndroid Build Coastguard Worker
132*84e33947SAndroid Build Coastguard Worker        # Bubble up "has_vla_member" to types each type it appears in, i.e. if this flag is set to
133*84e33947SAndroid Build Coastguard Worker        # True on a leaf node, then all its ancestors should also have the flag set to True
134*84e33947SAndroid Build Coastguard Worker        for type_name, type_info in self.structs_and_unions.items():
135*84e33947SAndroid Build Coastguard Worker            if type_info['has_vla_member']:
136*84e33947SAndroid Build Coastguard Worker                types_to_mark = list(type_info['appears_in'])
137*84e33947SAndroid Build Coastguard Worker                while len(types_to_mark) > 0:
138*84e33947SAndroid Build Coastguard Worker                    type_to_mark = types_to_mark.pop()
139*84e33947SAndroid Build Coastguard Worker                    self.structs_and_unions[type_to_mark]['has_vla_member'] = True
140*84e33947SAndroid Build Coastguard Worker                    types_to_mark.extend(
141*84e33947SAndroid Build Coastguard Worker                        list(self.structs_and_unions[type_to_mark]['appears_in']))
142*84e33947SAndroid Build Coastguard Worker
143*84e33947SAndroid Build Coastguard Worker    def _parse_api(self):
144*84e33947SAndroid Build Coastguard Worker        """
145*84e33947SAndroid Build Coastguard Worker        Parses the API and stores the structs and unions.
146*84e33947SAndroid Build Coastguard Worker        """
147*84e33947SAndroid Build Coastguard Worker
148*84e33947SAndroid Build Coastguard Worker        file_to_parse = self._files_to_parse()
149*84e33947SAndroid Build Coastguard Worker        self.parser = CParser(file_to_parse, cache='parser_cache')
150*84e33947SAndroid Build Coastguard Worker        self._parse_structs_and_unions()
151