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