xref: /aosp_15_r20/build/soong/cc/ndkstubgen/__init__.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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
21from pathlib import Path
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,
32                 symbol_list: TextIO, filt: symbolfile.Filter) -> None:
33        self.src_file = src_file
34        self.version_script = version_script
35        self.symbol_list = symbol_list
36        self.filter = filt
37        self.api = filt.api
38
39    def write(self, versions: Iterable[Version]) -> None:
40        """Writes all symbol data to the output files."""
41        self.symbol_list.write('[abi_symbol_list]\n')
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 self.filter.should_omit_version(version):
48            return
49
50        section_versioned = symbolfile.symbol_versioned_in_api(
51            version.tags, self.api)
52        version_empty = True
53        pruned_symbols = []
54        for symbol in version.symbols:
55            if self.filter.should_omit_symbol(symbol):
56                continue
57
58            if symbolfile.symbol_versioned_in_api(symbol.tags, self.api):
59                version_empty = False
60            pruned_symbols.append(symbol)
61
62        if len(pruned_symbols) > 0:
63            if not version_empty and section_versioned:
64                self.version_script.write(version.name + ' {\n')
65                self.version_script.write('    global:\n')
66            for symbol in pruned_symbols:
67                emit_version = symbolfile.symbol_versioned_in_api(
68                    symbol.tags, self.api)
69                if section_versioned and emit_version:
70                    self.version_script.write('        ' + symbol.name + ';\n')
71
72                weak = ''
73                if 'weak' in symbol.tags:
74                    weak = '__attribute__((weak)) '
75
76                if 'var' in symbol.tags:
77                    self.src_file.write(f'{weak}int {symbol.name} = 0;\n')
78                else:
79                    self.src_file.write(f'{weak}void {symbol.name}() {{}}\n')
80
81                self.symbol_list.write(f'{symbol.name}\n')
82
83            if not version_empty and section_versioned:
84                base = '' if version.base is None else ' ' + version.base
85                self.version_script.write('}' + base + ';\n')
86
87
88def parse_args() -> argparse.Namespace:
89    """Parses and returns command line arguments."""
90    parser = argparse.ArgumentParser()
91
92    def resolved_path(raw: str) -> Path:
93        """Returns a resolved Path for the given string."""
94        return Path(raw).resolve()
95
96    parser.add_argument('-v', '--verbose', action='count', default=0)
97
98    parser.add_argument(
99        '--api', required=True, help='API level being targeted.')
100    parser.add_argument(
101        '--arch', choices=symbolfile.ALL_ARCHITECTURES, required=True,
102        help='Architecture being targeted.')
103    parser.add_argument(
104        '--llndk', action='store_true', help='Use the LLNDK variant.')
105    parser.add_argument(
106        '--apex',
107        action='store_true',
108        help='Use the APEX variant.')
109    parser.add_argument(
110        '--systemapi',
111        action='store_true',
112        dest='systemapi',
113        help='Use the SystemAPI variant.')
114    parser.add_argument(
115        '--no-ndk',
116        action='store_false',
117        dest='ndk',
118        help='Do not include NDK APIs.')
119
120    parser.add_argument('--api-map',
121                        type=resolved_path,
122                        required=True,
123                        help='Path to the API level map JSON file.')
124
125    parser.add_argument('symbol_file',
126                        type=resolved_path,
127                        help='Path to symbol file.')
128    parser.add_argument('stub_src',
129                        type=resolved_path,
130                        help='Path to output stub source file.')
131    parser.add_argument('version_script',
132                        type=resolved_path,
133                        help='Path to output version script.')
134    parser.add_argument('symbol_list',
135                        type=resolved_path,
136                        help='Path to output abigail symbol list.')
137
138    return parser.parse_args()
139
140
141def main() -> None:
142    """Program entry point."""
143    args = parse_args()
144
145    with args.api_map.open() as map_file:
146        api_map = json.load(map_file)
147    api = symbolfile.decode_api_level(args.api, api_map)
148
149    verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
150    verbosity = args.verbose
151    if verbosity > 2:
152        verbosity = 2
153    logging.basicConfig(level=verbose_map[verbosity])
154
155    filt = symbolfile.Filter(args.arch, api, args.llndk, args.apex, args.systemapi, args.ndk)
156    with args.symbol_file.open() as symbol_file:
157        try:
158          versions = symbolfile.SymbolFileParser(symbol_file, api_map, filt).parse()
159        except symbolfile.MultiplyDefinedSymbolError as ex:
160            sys.exit(f'{args.symbol_file}: error: {ex}')
161
162    with args.stub_src.open('w') as src_file:
163        with args.version_script.open('w') as version_script:
164            with args.symbol_list.open('w') as symbol_list:
165                generator = Generator(src_file, version_script, symbol_list,
166                                      filt)
167                generator.write(versions)
168
169
170if __name__ == '__main__':
171    main()
172