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