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