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