1#!/usr/bin/env python
2#
3# Copyright (C) 2016 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 source for stub shared libraries for the NDK."""
18import argparse
19import json
20import logging
21import os
22import sys
23from typing import Iterable, TextIO
24
25import symbolfile
26from symbolfile import Arch, Version
27
28
29class Generator:
30    """Output generator that writes stub source files and version scripts."""
31    def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch,
32                 api: int, llndk: bool, apex: bool) -> None:
33        self.src_file = src_file
34        self.version_script = version_script
35        self.arch = arch
36        self.api = api
37        self.llndk = llndk
38        self.apex = apex
39
40    def write(self, versions: Iterable[Version]) -> None:
41        """Writes all symbol data to the output files."""
42        for version in versions:
43            self.write_version(version)
44
45    def write_version(self, version: Version) -> None:
46        """Writes a single version block's data to the output files."""
47        if symbolfile.should_omit_version(version, self.arch, self.api,
48                                          self.llndk, self.apex):
49            return
50
51        section_versioned = symbolfile.symbol_versioned_in_api(
52            version.tags, self.api)
53        version_empty = True
54        pruned_symbols = []
55        for symbol in version.symbols:
56            if symbolfile.should_omit_symbol(symbol, self.arch, self.api,
57                                             self.llndk, self.apex):
58                continue
59
60            if symbolfile.symbol_versioned_in_api(symbol.tags, self.api):
61                version_empty = False
62            pruned_symbols.append(symbol)
63
64        if len(pruned_symbols) > 0:
65            if not version_empty and section_versioned:
66                self.version_script.write(version.name + ' {\n')
67                self.version_script.write('    global:\n')
68            for symbol in pruned_symbols:
69                emit_version = symbolfile.symbol_versioned_in_api(
70                    symbol.tags, self.api)
71                if section_versioned and emit_version:
72                    self.version_script.write('        ' + symbol.name + ';\n')
73
74                weak = ''
75                if 'weak' in symbol.tags:
76                    weak = '__attribute__((weak)) '
77
78                if 'var' in symbol.tags:
79                    self.src_file.write('{}int {} = 0;\n'.format(
80                        weak, symbol.name))
81                else:
82                    self.src_file.write('{}void {}() {{}}\n'.format(
83                        weak, symbol.name))
84
85            if not version_empty and section_versioned:
86                base = '' if version.base is None else ' ' + version.base
87                self.version_script.write('}' + base + ';\n')
88
89
90def parse_args() -> argparse.Namespace:
91    """Parses and returns command line arguments."""
92    parser = argparse.ArgumentParser()
93
94    parser.add_argument('-v', '--verbose', action='count', default=0)
95
96    parser.add_argument(
97        '--api', required=True, help='API level being targeted.')
98    parser.add_argument(
99        '--arch', choices=symbolfile.ALL_ARCHITECTURES, required=True,
100        help='Architecture being targeted.')
101    parser.add_argument(
102        '--llndk', action='store_true', help='Use the LLNDK variant.')
103    parser.add_argument(
104        '--apex', action='store_true', help='Use the APEX variant.')
105
106    # https://github.com/python/mypy/issues/1317
107    # mypy has issues with using os.path.realpath as an argument here.
108    parser.add_argument(
109        '--api-map',
110        type=os.path.realpath,  # type: ignore
111        required=True,
112        help='Path to the API level map JSON file.')
113
114    parser.add_argument(
115        'symbol_file',
116        type=os.path.realpath,  # type: ignore
117        help='Path to symbol file.')
118    parser.add_argument(
119        'stub_src',
120        type=os.path.realpath,  # type: ignore
121        help='Path to output stub source file.')
122    parser.add_argument(
123        'version_script',
124        type=os.path.realpath,  # type: ignore
125        help='Path to output version script.')
126
127    return parser.parse_args()
128
129
130def main() -> None:
131    """Program entry point."""
132    args = parse_args()
133
134    with open(args.api_map) as map_file:
135        api_map = json.load(map_file)
136    api = symbolfile.decode_api_level(args.api, api_map)
137
138    verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
139    verbosity = args.verbose
140    if verbosity > 2:
141        verbosity = 2
142    logging.basicConfig(level=verbose_map[verbosity])
143
144    with open(args.symbol_file) as symbol_file:
145        try:
146            versions = symbolfile.SymbolFileParser(symbol_file, api_map,
147                                                   args.arch, api, args.llndk,
148                                                   args.apex).parse()
149        except symbolfile.MultiplyDefinedSymbolError as ex:
150            sys.exit('{}: error: {}'.format(args.symbol_file, ex))
151
152    with open(args.stub_src, 'w') as src_file:
153        with open(args.version_script, 'w') as version_file:
154            generator = Generator(src_file, version_file, args.arch, api,
155                                  args.llndk, args.apex)
156            generator.write(versions)
157
158
159if __name__ == '__main__':
160    main()
161