• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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