sophiez | b858c6d | 2020-05-06 15:57:32 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright (C) 2020 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 | """Generates xml of NDK libraries used for API coverage analysis.""" |
| 18 | import argparse |
| 19 | import json |
| 20 | import os |
| 21 | import sys |
| 22 | |
| 23 | from xml.etree.ElementTree import Element, SubElement, tostring |
| 24 | from gen_stub_libs import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser |
| 25 | |
| 26 | |
| 27 | ROOT_ELEMENT_TAG = 'ndk-library' |
| 28 | SYMBOL_ELEMENT_TAG = 'symbol' |
| 29 | ARCHITECTURE_ATTRIBUTE_KEY = 'arch' |
| 30 | DEPRECATED_ATTRIBUTE_KEY = 'is_deprecated' |
| 31 | PLATFORM_ATTRIBUTE_KEY = 'is_platform' |
| 32 | NAME_ATTRIBUTE_KEY = 'name' |
| 33 | VARIABLE_TAG = 'var' |
| 34 | EXPOSED_TARGET_TAGS = ( |
| 35 | 'vndk', |
| 36 | 'apex', |
| 37 | 'llndk', |
| 38 | ) |
| 39 | API_LEVEL_TAG_PREFIXES = ( |
| 40 | 'introduced=', |
| 41 | 'introduced-', |
| 42 | ) |
| 43 | |
| 44 | |
| 45 | def parse_tags(tags): |
| 46 | """Parses tags and save needed tags in the created attributes. |
| 47 | |
| 48 | Return attributes dictionary. |
| 49 | """ |
| 50 | attributes = {} |
| 51 | arch = [] |
| 52 | for tag in tags: |
| 53 | if tag.startswith(tuple(API_LEVEL_TAG_PREFIXES)): |
| 54 | key, _, value = tag.partition('=') |
| 55 | attributes.update({key: value}) |
| 56 | elif tag in ALL_ARCHITECTURES: |
| 57 | arch.append(tag) |
| 58 | elif tag in EXPOSED_TARGET_TAGS: |
| 59 | attributes.update({tag: 'True'}) |
| 60 | attributes.update({ARCHITECTURE_ATTRIBUTE_KEY: ','.join(arch)}) |
| 61 | return attributes |
| 62 | |
| 63 | |
| 64 | class XmlGenerator(object): |
| 65 | """Output generator that writes parsed symbol file to a xml file.""" |
| 66 | def __init__(self, output_file): |
| 67 | self.output_file = output_file |
| 68 | |
| 69 | def convertToXml(self, versions): |
| 70 | """Writes all symbol data to the output file.""" |
| 71 | root = Element(ROOT_ELEMENT_TAG) |
| 72 | for version in versions: |
| 73 | if VARIABLE_TAG in version.tags: |
| 74 | continue |
| 75 | version_attributes = parse_tags(version.tags) |
| 76 | _, _, postfix = version.name.partition('_') |
| 77 | is_platform = postfix == 'PRIVATE' or postfix == 'PLATFORM' |
| 78 | is_deprecated = postfix == 'DEPRECATED' |
| 79 | version_attributes.update({PLATFORM_ATTRIBUTE_KEY: str(is_platform)}) |
| 80 | version_attributes.update({DEPRECATED_ATTRIBUTE_KEY: str(is_deprecated)}) |
| 81 | for symbol in version.symbols: |
| 82 | if VARIABLE_TAG in symbol.tags: |
| 83 | continue |
| 84 | attributes = {NAME_ATTRIBUTE_KEY: symbol.name} |
| 85 | attributes.update(version_attributes) |
| 86 | # If same version tags already exist, it will be overwrite here. |
| 87 | attributes.update(parse_tags(symbol.tags)) |
| 88 | SubElement(root, SYMBOL_ELEMENT_TAG, attributes) |
| 89 | return root |
| 90 | |
| 91 | def write_xml_to_file(self, root): |
| 92 | """Write xml element root to output_file.""" |
| 93 | parsed_data = tostring(root) |
| 94 | output_file = open(self.output_file, "wb") |
| 95 | output_file.write(parsed_data) |
| 96 | |
| 97 | def write(self, versions): |
| 98 | root = self.convertToXml(versions) |
| 99 | self.write_xml_to_file(root) |
| 100 | |
| 101 | |
| 102 | def parse_args(): |
| 103 | """Parses and returns command line arguments.""" |
| 104 | parser = argparse.ArgumentParser() |
| 105 | |
| 106 | parser.add_argument('symbol_file', type=os.path.realpath, help='Path to symbol file.') |
| 107 | parser.add_argument( |
| 108 | 'output_file', type=os.path.realpath, |
| 109 | help='The output parsed api coverage file.') |
| 110 | parser.add_argument( |
| 111 | '--api-map', type=os.path.realpath, required=True, |
| 112 | help='Path to the API level map JSON file.') |
| 113 | return parser.parse_args() |
| 114 | |
| 115 | |
| 116 | def main(): |
| 117 | """Program entry point.""" |
| 118 | args = parse_args() |
| 119 | |
| 120 | with open(args.api_map) as map_file: |
| 121 | api_map = json.load(map_file) |
| 122 | |
| 123 | with open(args.symbol_file) as symbol_file: |
| 124 | try: |
| 125 | versions = SymbolFileParser(symbol_file, api_map, "", FUTURE_API_LEVEL, |
| 126 | True, True).parse() |
| 127 | except MultiplyDefinedSymbolError as ex: |
| 128 | sys.exit('{}: error: {}'.format(args.symbol_file, ex)) |
| 129 | |
| 130 | generator = XmlGenerator(args.output_file) |
| 131 | generator.write(versions) |
| 132 | |
| 133 | if __name__ == '__main__': |
| 134 | main() |