| #!/usr/bin/python3 |
| # |
| # Copyright (C) 2023 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. |
| # |
| |
| import os |
| |
| from collections import defaultdict |
| from pyclibrary import CParser |
| |
| from utils import system_chre_abs_path |
| |
| |
| class ApiParser: |
| """Given a file-specific set of annotations (extracted from JSON annotations file), parses a |
| single API header file into data structures suitable for use with code generation. This class |
| will contain the parsed representation of the headers when instantiated. |
| """ |
| |
| def __init__(self, json_obj): |
| """Initialize and parse the API file described in the provided JSON-derived object. |
| |
| :param json_obj: Extracted file-specific annotations from JSON |
| """ |
| |
| self.json = json_obj |
| self.structs_and_unions = {} |
| self._parse_annotations() |
| self._parse_api() |
| |
| def _parse_annotations(self): |
| # Converts annotations list to a more usable data structure: dict keyed by structure name, |
| # containing a dict keyed by field name, containing a list of annotations (as they |
| # appear in the JSON). In other words, we can easily get all of the annotations for the |
| # "version" field in "chreWwanCellInfoResult" via |
| # annotations['chreWwanCellInfoResult']['version']. This is also a defaultdict, so it's safe |
| # to access if there are no annotations for this structure + field; it'll just give you |
| # an empty list in that case. |
| |
| self.annotations = defaultdict(lambda: defaultdict(list)) |
| for struct_info in self.json['struct_info']: |
| for annotation in struct_info['annotations']: |
| self.annotations[struct_info['name'] |
| ][annotation['field']].append(annotation) |
| |
| def _files_to_parse(self): |
| """Returns a list of files to supply as input to CParser""" |
| |
| # Input paths for CParser are stored in JSON relative to <android_root>/system/chre |
| # Reformulate these to absolute paths, and add in some default includes that we always |
| # supply |
| chre_project_base_dir = system_chre_abs_path() |
| default_includes = ['api_parser/parser_defines.h', |
| 'chre_api/include/chre_api/chre/version.h'] |
| files = default_includes + \ |
| self.json['includes'] + [self.json['filename']] |
| return [os.path.join(chre_project_base_dir, file) for file in files] |
| |
| def _parse_structs_and_unions(self): |
| # Starts with the root structures (i.e. those that will appear at the top-level in one |
| # or more CHPP messages), build a data structure containing all of the information we'll |
| # need to emit the CHPP structure definition and conversion code. |
| |
| structs_and_unions_to_parse = self.json['root_structs'].copy() |
| while len(structs_and_unions_to_parse) > 0: |
| type_name = structs_and_unions_to_parse.pop() |
| if type_name in self.structs_and_unions: |
| continue |
| |
| entry = { |
| 'appears_in': set(), # Other types this type is nested within |
| 'dependencies': set(), # Types that are nested in this type |
| 'has_vla_member': False, # True if this type or any dependency has a VLA member |
| 'members': [], # Info about each member of this type |
| } |
| if type_name in self.parser.defs['structs']: |
| defs = self.parser.defs['structs'][type_name] |
| entry['is_union'] = False |
| elif type_name in self.parser.defs['unions']: |
| defs = self.parser.defs['unions'][type_name] |
| entry['is_union'] = True |
| else: |
| raise RuntimeError( |
| "Couldn't find {} in parsed structs/unions".format(type_name)) |
| |
| for member_name, member_type, _ in defs['members']: |
| member_info = { |
| 'name': member_name, |
| 'type': member_type, |
| 'annotations': self.annotations[type_name][member_name], |
| 'is_nested_type': False, |
| } |
| |
| if member_type.type_spec.startswith('struct ') or \ |
| member_type.type_spec.startswith('union '): |
| member_info['is_nested_type'] = True |
| member_type_name = member_type.type_spec.split(' ')[1] |
| member_info['nested_type_name'] = member_type_name |
| entry['dependencies'].add(member_type_name) |
| structs_and_unions_to_parse.append(member_type_name) |
| |
| entry['members'].append(member_info) |
| |
| # Flip a flag if this structure has at least one variable-length array member, which |
| # means that the encoded size can only be computed at runtime |
| if not entry['has_vla_member']: |
| for annotation in self.annotations[type_name][member_name]: |
| if annotation['annotation'] == 'var_len_array': |
| entry['has_vla_member'] = True |
| |
| self.structs_and_unions[type_name] = entry |
| |
| # Build reverse linkage of dependency chain (i.e. lookup between a type and the other types |
| # it appears in) |
| for type_name, type_info in self.structs_and_unions.items(): |
| for dependency in type_info['dependencies']: |
| self.structs_and_unions[dependency]['appears_in'].add( |
| type_name) |
| |
| # Bubble up "has_vla_member" to types each type it appears in, i.e. if this flag is set to |
| # True on a leaf node, then all its ancestors should also have the flag set to True |
| for type_name, type_info in self.structs_and_unions.items(): |
| if type_info['has_vla_member']: |
| types_to_mark = list(type_info['appears_in']) |
| while len(types_to_mark) > 0: |
| type_to_mark = types_to_mark.pop() |
| self.structs_and_unions[type_to_mark]['has_vla_member'] = True |
| types_to_mark.extend( |
| list(self.structs_and_unions[type_to_mark]['appears_in'])) |
| |
| def _parse_api(self): |
| """ |
| Parses the API and stores the structs and unions. |
| """ |
| |
| file_to_parse = self._files_to_parse() |
| self.parser = CParser(file_to_parse, cache='parser_cache') |
| self._parse_structs_and_unions() |