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