| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 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.""" | 
|  | 18 | import argparse | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 19 | import json | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 20 | import logging | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 21 | import os | 
|  | 22 | import re | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 23 | import sys | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 24 |  | 
|  | 25 |  | 
|  | 26 | ALL_ARCHITECTURES = ( | 
|  | 27 | 'arm', | 
|  | 28 | 'arm64', | 
|  | 29 | 'mips', | 
|  | 30 | 'mips64', | 
|  | 31 | 'x86', | 
|  | 32 | 'x86_64', | 
|  | 33 | ) | 
|  | 34 |  | 
|  | 35 |  | 
| Dan Albert | fd86e9e | 2016-11-08 13:35:12 -0800 | [diff] [blame] | 36 | # Arbitrary magic number. We use the same one in api-level.h for this purpose. | 
|  | 37 | FUTURE_API_LEVEL = 10000 | 
|  | 38 |  | 
|  | 39 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 40 | def logger(): | 
|  | 41 | """Return the main logger for this module.""" | 
|  | 42 | return logging.getLogger(__name__) | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 43 |  | 
|  | 44 |  | 
| Dan Albert | a85042a | 2016-07-28 16:58:27 -0700 | [diff] [blame] | 45 | def get_tags(line): | 
|  | 46 | """Returns a list of all tags on this line.""" | 
|  | 47 | _, _, all_tags = line.strip().partition('#') | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 48 | return [e for e in re.split(r'\s+', all_tags) if e.strip()] | 
| Dan Albert | a85042a | 2016-07-28 16:58:27 -0700 | [diff] [blame] | 49 |  | 
|  | 50 |  | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 51 | def is_api_level_tag(tag): | 
|  | 52 | """Returns true if this tag has an API level that may need decoding.""" | 
|  | 53 | if tag.startswith('introduced='): | 
|  | 54 | return True | 
|  | 55 | if tag.startswith('introduced-'): | 
|  | 56 | return True | 
|  | 57 | if tag.startswith('versioned='): | 
|  | 58 | return True | 
|  | 59 | return False | 
|  | 60 |  | 
|  | 61 |  | 
|  | 62 | def decode_api_level_tags(tags, api_map): | 
|  | 63 | """Decodes API level code names in a list of tags. | 
|  | 64 |  | 
|  | 65 | Raises: | 
|  | 66 | ParseError: An unknown version name was found in a tag. | 
|  | 67 | """ | 
|  | 68 | for idx, tag in enumerate(tags): | 
|  | 69 | if not is_api_level_tag(tag): | 
|  | 70 | continue | 
|  | 71 | name, value = split_tag(tag) | 
|  | 72 |  | 
|  | 73 | try: | 
|  | 74 | decoded = str(decode_api_level(value, api_map)) | 
|  | 75 | tags[idx] = '='.join([name, decoded]) | 
|  | 76 | except KeyError: | 
|  | 77 | raise ParseError('Unknown version name in tag: {}'.format(tag)) | 
|  | 78 | return tags | 
|  | 79 |  | 
|  | 80 |  | 
|  | 81 | def split_tag(tag): | 
|  | 82 | """Returns a key/value tuple of the tag. | 
|  | 83 |  | 
|  | 84 | Raises: | 
|  | 85 | ValueError: Tag is not a key/value type tag. | 
|  | 86 |  | 
|  | 87 | Returns: Tuple of (key, value) of the tag. Both components are strings. | 
|  | 88 | """ | 
|  | 89 | if '=' not in tag: | 
|  | 90 | raise ValueError('Not a key/value tag: ' + tag) | 
|  | 91 | key, _, value = tag.partition('=') | 
|  | 92 | return key, value | 
|  | 93 |  | 
|  | 94 |  | 
| Dan Albert | c42458e | 2016-07-29 13:05:39 -0700 | [diff] [blame] | 95 | def get_tag_value(tag): | 
|  | 96 | """Returns the value of a key/value tag. | 
|  | 97 |  | 
|  | 98 | Raises: | 
|  | 99 | ValueError: Tag is not a key/value type tag. | 
|  | 100 |  | 
|  | 101 | Returns: Value part of tag as a string. | 
|  | 102 | """ | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 103 | return split_tag(tag)[1] | 
| Dan Albert | c42458e | 2016-07-29 13:05:39 -0700 | [diff] [blame] | 104 |  | 
|  | 105 |  | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 106 | def version_is_private(version): | 
|  | 107 | """Returns True if the version name should be treated as private.""" | 
|  | 108 | return version.endswith('_PRIVATE') or version.endswith('_PLATFORM') | 
|  | 109 |  | 
|  | 110 |  | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 111 | def should_omit_version(version, arch, api, vndk, apex): | 
| Dan Albert | 08532b6 | 2016-07-28 18:09:47 -0700 | [diff] [blame] | 112 | """Returns True if the version section should be ommitted. | 
|  | 113 |  | 
|  | 114 | We want to omit any sections that do not have any symbols we'll have in the | 
|  | 115 | stub library. Sections that contain entirely future symbols or only symbols | 
|  | 116 | for certain architectures. | 
|  | 117 | """ | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 118 | if version_is_private(version.name): | 
| Dan Albert | 08532b6 | 2016-07-28 18:09:47 -0700 | [diff] [blame] | 119 | return True | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 120 | if 'platform-only' in version.tags: | 
| Dan Albert | 300cb2f | 2016-11-04 14:52:30 -0700 | [diff] [blame] | 121 | return True | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 122 | if 'vndk' in version.tags and not vndk: | 
| Dan Willemsen | b01e7f7 | 2017-04-03 14:28:36 -0700 | [diff] [blame] | 123 | return True | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 124 | if 'apex' in version.tags and not apex: | 
|  | 125 | return True | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 126 | if not symbol_in_arch(version.tags, arch): | 
| Dan Albert | 08532b6 | 2016-07-28 18:09:47 -0700 | [diff] [blame] | 127 | return True | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 128 | if not symbol_in_api(version.tags, arch, api): | 
|  | 129 | return True | 
|  | 130 | return False | 
|  | 131 |  | 
|  | 132 |  | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 133 | def should_omit_symbol(symbol, arch, api, vndk, apex): | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 134 | """Returns True if the symbol should be omitted.""" | 
|  | 135 | if not vndk and 'vndk' in symbol.tags: | 
|  | 136 | return True | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 137 | if not apex and 'apex' in symbol.tags: | 
|  | 138 | return True | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 139 | if not symbol_in_arch(symbol.tags, arch): | 
|  | 140 | return True | 
|  | 141 | if not symbol_in_api(symbol.tags, arch, api): | 
| Dan Albert | 08532b6 | 2016-07-28 18:09:47 -0700 | [diff] [blame] | 142 | return True | 
|  | 143 | return False | 
|  | 144 |  | 
|  | 145 |  | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 146 | def symbol_in_arch(tags, arch): | 
|  | 147 | """Returns true if the symbol is present for the given architecture.""" | 
|  | 148 | has_arch_tags = False | 
|  | 149 | for tag in tags: | 
|  | 150 | if tag == arch: | 
|  | 151 | return True | 
|  | 152 | if tag in ALL_ARCHITECTURES: | 
|  | 153 | has_arch_tags = True | 
|  | 154 |  | 
|  | 155 | # If there were no arch tags, the symbol is available for all | 
|  | 156 | # architectures. If there were any arch tags, the symbol is only available | 
|  | 157 | # for the tagged architectures. | 
|  | 158 | return not has_arch_tags | 
|  | 159 |  | 
|  | 160 |  | 
| Dan Albert | c42458e | 2016-07-29 13:05:39 -0700 | [diff] [blame] | 161 | def symbol_in_api(tags, arch, api): | 
|  | 162 | """Returns true if the symbol is present for the given API level.""" | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 163 | introduced_tag = None | 
|  | 164 | arch_specific = False | 
|  | 165 | for tag in tags: | 
|  | 166 | # If there is an arch-specific tag, it should override the common one. | 
|  | 167 | if tag.startswith('introduced=') and not arch_specific: | 
|  | 168 | introduced_tag = tag | 
|  | 169 | elif tag.startswith('introduced-' + arch + '='): | 
|  | 170 | introduced_tag = tag | 
|  | 171 | arch_specific = True | 
| Dan Albert | a85042a | 2016-07-28 16:58:27 -0700 | [diff] [blame] | 172 | elif tag == 'future': | 
| Dan Albert | fd86e9e | 2016-11-08 13:35:12 -0800 | [diff] [blame] | 173 | return api == FUTURE_API_LEVEL | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 174 |  | 
|  | 175 | if introduced_tag is None: | 
|  | 176 | # We found no "introduced" tags, so the symbol has always been | 
|  | 177 | # available. | 
|  | 178 | return True | 
|  | 179 |  | 
| Dan Albert | c42458e | 2016-07-29 13:05:39 -0700 | [diff] [blame] | 180 | return api >= int(get_tag_value(introduced_tag)) | 
|  | 181 |  | 
|  | 182 |  | 
|  | 183 | def symbol_versioned_in_api(tags, api): | 
|  | 184 | """Returns true if the symbol should be versioned for the given API. | 
|  | 185 |  | 
|  | 186 | This models the `versioned=API` tag. This should be a very uncommonly | 
|  | 187 | needed tag, and is really only needed to fix versioning mistakes that are | 
|  | 188 | already out in the wild. | 
|  | 189 |  | 
|  | 190 | For example, some of libc's __aeabi_* functions were originally placed in | 
|  | 191 | the private version, but that was incorrect. They are now in LIBC_N, but | 
|  | 192 | when building against any version prior to N we need the symbol to be | 
|  | 193 | unversioned (otherwise it won't resolve on M where it is private). | 
|  | 194 | """ | 
|  | 195 | for tag in tags: | 
|  | 196 | if tag.startswith('versioned='): | 
|  | 197 | return api >= int(get_tag_value(tag)) | 
|  | 198 | # If there is no "versioned" tag, the tag has been versioned for as long as | 
|  | 199 | # it was introduced. | 
|  | 200 | return True | 
|  | 201 |  | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 202 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 203 | class ParseError(RuntimeError): | 
|  | 204 | """An error that occurred while parsing a symbol file.""" | 
|  | 205 | pass | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 206 |  | 
|  | 207 |  | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 208 | class MultiplyDefinedSymbolError(RuntimeError): | 
|  | 209 | """A symbol name was multiply defined.""" | 
|  | 210 | def __init__(self, multiply_defined_symbols): | 
|  | 211 | super(MultiplyDefinedSymbolError, self).__init__( | 
|  | 212 | 'Version script contains multiple definitions for: {}'.format( | 
|  | 213 | ', '.join(multiply_defined_symbols))) | 
|  | 214 | self.multiply_defined_symbols = multiply_defined_symbols | 
|  | 215 |  | 
|  | 216 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 217 | class Version(object): | 
|  | 218 | """A version block of a symbol file.""" | 
|  | 219 | def __init__(self, name, base, tags, symbols): | 
|  | 220 | self.name = name | 
|  | 221 | self.base = base | 
|  | 222 | self.tags = tags | 
|  | 223 | self.symbols = symbols | 
|  | 224 |  | 
|  | 225 | def __eq__(self, other): | 
|  | 226 | if self.name != other.name: | 
|  | 227 | return False | 
|  | 228 | if self.base != other.base: | 
|  | 229 | return False | 
|  | 230 | if self.tags != other.tags: | 
|  | 231 | return False | 
|  | 232 | if self.symbols != other.symbols: | 
|  | 233 | return False | 
|  | 234 | return True | 
|  | 235 |  | 
|  | 236 |  | 
|  | 237 | class Symbol(object): | 
|  | 238 | """A symbol definition from a symbol file.""" | 
|  | 239 | def __init__(self, name, tags): | 
|  | 240 | self.name = name | 
|  | 241 | self.tags = tags | 
|  | 242 |  | 
|  | 243 | def __eq__(self, other): | 
|  | 244 | return self.name == other.name and set(self.tags) == set(other.tags) | 
|  | 245 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 246 | class SymbolFileParser(object): | 
|  | 247 | """Parses NDK symbol files.""" | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 248 | def __init__(self, input_file, api_map, arch, api, vndk, apex): | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 249 | self.input_file = input_file | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 250 | self.api_map = api_map | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 251 | self.arch = arch | 
|  | 252 | self.api = api | 
|  | 253 | self.vndk = vndk | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 254 | self.apex = apex | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 255 | self.current_line = None | 
|  | 256 |  | 
|  | 257 | def parse(self): | 
|  | 258 | """Parses the symbol file and returns a list of Version objects.""" | 
|  | 259 | versions = [] | 
|  | 260 | while self.next_line() != '': | 
|  | 261 | if '{' in self.current_line: | 
|  | 262 | versions.append(self.parse_version()) | 
|  | 263 | else: | 
|  | 264 | raise ParseError( | 
|  | 265 | 'Unexpected contents at top level: ' + self.current_line) | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 266 |  | 
|  | 267 | self.check_no_duplicate_symbols(versions) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 268 | return versions | 
|  | 269 |  | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 270 | def check_no_duplicate_symbols(self, versions): | 
|  | 271 | """Raises errors for multiply defined symbols. | 
|  | 272 |  | 
|  | 273 | This situation is the normal case when symbol versioning is actually | 
|  | 274 | used, but this script doesn't currently handle that. The error message | 
|  | 275 | will be a not necessarily obvious "error: redefition of 'foo'" from | 
|  | 276 | stub.c, so it's better for us to catch this situation and raise a | 
|  | 277 | better error. | 
|  | 278 | """ | 
|  | 279 | symbol_names = set() | 
|  | 280 | multiply_defined_symbols = set() | 
|  | 281 | for version in versions: | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 282 | if should_omit_version(version, self.arch, self.api, self.vndk, self.apex): | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 283 | continue | 
|  | 284 |  | 
|  | 285 | for symbol in version.symbols: | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 286 | if should_omit_symbol(symbol, self.arch, self.api, self.vndk, self.apex): | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 287 | continue | 
|  | 288 |  | 
|  | 289 | if symbol.name in symbol_names: | 
|  | 290 | multiply_defined_symbols.add(symbol.name) | 
|  | 291 | symbol_names.add(symbol.name) | 
|  | 292 | if multiply_defined_symbols: | 
|  | 293 | raise MultiplyDefinedSymbolError( | 
|  | 294 | sorted(list(multiply_defined_symbols))) | 
|  | 295 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 296 | def parse_version(self): | 
|  | 297 | """Parses a single version section and returns a Version object.""" | 
|  | 298 | name = self.current_line.split('{')[0].strip() | 
|  | 299 | tags = get_tags(self.current_line) | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 300 | tags = decode_api_level_tags(tags, self.api_map) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 301 | symbols = [] | 
|  | 302 | global_scope = True | 
| dimitry | 2be7fa9 | 2017-11-21 17:47:33 +0100 | [diff] [blame] | 303 | cpp_symbols = False | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 304 | while self.next_line() != '': | 
|  | 305 | if '}' in self.current_line: | 
|  | 306 | # Line is something like '} BASE; # tags'. Both base and tags | 
|  | 307 | # are optional here. | 
|  | 308 | base = self.current_line.partition('}')[2] | 
|  | 309 | base = base.partition('#')[0].strip() | 
|  | 310 | if not base.endswith(';'): | 
|  | 311 | raise ParseError( | 
| dimitry | 2be7fa9 | 2017-11-21 17:47:33 +0100 | [diff] [blame] | 312 | 'Unterminated version/export "C++" block (expected ;).') | 
|  | 313 | if cpp_symbols: | 
|  | 314 | cpp_symbols = False | 
|  | 315 | else: | 
|  | 316 | base = base.rstrip(';').rstrip() | 
|  | 317 | if base == '': | 
|  | 318 | base = None | 
|  | 319 | return Version(name, base, tags, symbols) | 
|  | 320 | elif 'extern "C++" {' in self.current_line: | 
|  | 321 | cpp_symbols = True | 
|  | 322 | elif not cpp_symbols and ':' in self.current_line: | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 323 | visibility = self.current_line.split(':')[0].strip() | 
|  | 324 | if visibility == 'local': | 
|  | 325 | global_scope = False | 
|  | 326 | elif visibility == 'global': | 
|  | 327 | global_scope = True | 
|  | 328 | else: | 
|  | 329 | raise ParseError('Unknown visiblity label: ' + visibility) | 
| dimitry | 2be7fa9 | 2017-11-21 17:47:33 +0100 | [diff] [blame] | 330 | elif global_scope and not cpp_symbols: | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 331 | symbols.append(self.parse_symbol()) | 
|  | 332 | else: | 
| Dan Albert | f50b6ce | 2018-09-25 13:39:25 -0700 | [diff] [blame] | 333 | # We're in a hidden scope or in 'extern "C++"' block. Ignore | 
|  | 334 | # everything. | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 335 | pass | 
|  | 336 | raise ParseError('Unexpected EOF in version block.') | 
|  | 337 |  | 
|  | 338 | def parse_symbol(self): | 
|  | 339 | """Parses a single symbol line and returns a Symbol object.""" | 
|  | 340 | if ';' not in self.current_line: | 
|  | 341 | raise ParseError( | 
|  | 342 | 'Expected ; to terminate symbol: ' + self.current_line) | 
|  | 343 | if '*' in self.current_line: | 
|  | 344 | raise ParseError( | 
|  | 345 | 'Wildcard global symbols are not permitted.') | 
|  | 346 | # Line is now in the format "<symbol-name>; # tags" | 
|  | 347 | name, _, _ = self.current_line.strip().partition(';') | 
|  | 348 | tags = get_tags(self.current_line) | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 349 | tags = decode_api_level_tags(tags, self.api_map) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 350 | return Symbol(name, tags) | 
|  | 351 |  | 
|  | 352 | def next_line(self): | 
|  | 353 | """Returns the next non-empty non-comment line. | 
|  | 354 |  | 
|  | 355 | A return value of '' indicates EOF. | 
|  | 356 | """ | 
|  | 357 | line = self.input_file.readline() | 
|  | 358 | while line.strip() == '' or line.strip().startswith('#'): | 
|  | 359 | line = self.input_file.readline() | 
|  | 360 |  | 
|  | 361 | # We want to skip empty lines, but '' indicates EOF. | 
|  | 362 | if line == '': | 
|  | 363 | break | 
|  | 364 | self.current_line = line | 
|  | 365 | return self.current_line | 
|  | 366 |  | 
|  | 367 |  | 
|  | 368 | class Generator(object): | 
|  | 369 | """Output generator that writes stub source files and version scripts.""" | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 370 | def __init__(self, src_file, version_script, arch, api, vndk, apex): | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 371 | self.src_file = src_file | 
|  | 372 | self.version_script = version_script | 
|  | 373 | self.arch = arch | 
|  | 374 | self.api = api | 
| Dan Willemsen | b01e7f7 | 2017-04-03 14:28:36 -0700 | [diff] [blame] | 375 | self.vndk = vndk | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 376 | self.apex = apex | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 377 |  | 
|  | 378 | def write(self, versions): | 
|  | 379 | """Writes all symbol data to the output files.""" | 
|  | 380 | for version in versions: | 
|  | 381 | self.write_version(version) | 
|  | 382 |  | 
|  | 383 | def write_version(self, version): | 
|  | 384 | """Writes a single version block's data to the output files.""" | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 385 | if should_omit_version(version, self.arch, self.api, self.vndk, self.apex): | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 386 | return | 
|  | 387 |  | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 388 | section_versioned = symbol_versioned_in_api(version.tags, self.api) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 389 | version_empty = True | 
|  | 390 | pruned_symbols = [] | 
|  | 391 | for symbol in version.symbols: | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 392 | if should_omit_symbol(symbol, self.arch, self.api, self.vndk, self.apex): | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 393 | continue | 
|  | 394 |  | 
|  | 395 | if symbol_versioned_in_api(symbol.tags, self.api): | 
|  | 396 | version_empty = False | 
|  | 397 | pruned_symbols.append(symbol) | 
|  | 398 |  | 
|  | 399 | if len(pruned_symbols) > 0: | 
| Dan Albert | ae452cc | 2017-01-03 14:27:41 -0800 | [diff] [blame] | 400 | if not version_empty and section_versioned: | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 401 | self.version_script.write(version.name + ' {\n') | 
|  | 402 | self.version_script.write('    global:\n') | 
|  | 403 | for symbol in pruned_symbols: | 
| Dan Albert | ae452cc | 2017-01-03 14:27:41 -0800 | [diff] [blame] | 404 | emit_version = symbol_versioned_in_api(symbol.tags, self.api) | 
|  | 405 | if section_versioned and emit_version: | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 406 | self.version_script.write('        ' + symbol.name + ';\n') | 
|  | 407 |  | 
| Dan Albert | f55f078 | 2017-07-28 11:00:22 -0700 | [diff] [blame] | 408 | weak = '' | 
|  | 409 | if 'weak' in symbol.tags: | 
|  | 410 | weak = '__attribute__((weak)) ' | 
|  | 411 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 412 | if 'var' in symbol.tags: | 
| Dan Albert | f55f078 | 2017-07-28 11:00:22 -0700 | [diff] [blame] | 413 | self.src_file.write('{}int {} = 0;\n'.format( | 
|  | 414 | weak, symbol.name)) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 415 | else: | 
| Dan Albert | f55f078 | 2017-07-28 11:00:22 -0700 | [diff] [blame] | 416 | self.src_file.write('{}void {}() {{}}\n'.format( | 
|  | 417 | weak, symbol.name)) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 418 |  | 
| Dan Albert | ae452cc | 2017-01-03 14:27:41 -0800 | [diff] [blame] | 419 | if not version_empty and section_versioned: | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 420 | base = '' if version.base is None else ' ' + version.base | 
|  | 421 | self.version_script.write('}' + base + ';\n') | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 422 |  | 
|  | 423 |  | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 424 | def decode_api_level(api, api_map): | 
|  | 425 | """Decodes the API level argument into the API level number. | 
|  | 426 |  | 
|  | 427 | For the average case, this just decodes the integer value from the string, | 
|  | 428 | but for unreleased APIs we need to translate from the API codename (like | 
|  | 429 | "O") to the future API level for that codename. | 
|  | 430 | """ | 
|  | 431 | try: | 
|  | 432 | return int(api) | 
|  | 433 | except ValueError: | 
|  | 434 | pass | 
|  | 435 |  | 
|  | 436 | if api == "current": | 
|  | 437 | return FUTURE_API_LEVEL | 
|  | 438 |  | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 439 | return api_map[api] | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 440 |  | 
|  | 441 |  | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 442 | def parse_args(): | 
|  | 443 | """Parses and returns command line arguments.""" | 
|  | 444 | parser = argparse.ArgumentParser() | 
|  | 445 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 446 | parser.add_argument('-v', '--verbose', action='count', default=0) | 
|  | 447 |  | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 448 | parser.add_argument( | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 449 | '--api', required=True, help='API level being targeted.') | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 450 | parser.add_argument( | 
|  | 451 | '--arch', choices=ALL_ARCHITECTURES, required=True, | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 452 | help='Architecture being targeted.') | 
| Dan Willemsen | b01e7f7 | 2017-04-03 14:28:36 -0700 | [diff] [blame] | 453 | parser.add_argument( | 
|  | 454 | '--vndk', action='store_true', help='Use the VNDK variant.') | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 455 | parser.add_argument( | 
|  | 456 | '--apex', action='store_true', help='Use the APEX variant.') | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 457 |  | 
|  | 458 | parser.add_argument( | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 459 | '--api-map', type=os.path.realpath, required=True, | 
|  | 460 | help='Path to the API level map JSON file.') | 
|  | 461 |  | 
|  | 462 | parser.add_argument( | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 463 | 'symbol_file', type=os.path.realpath, help='Path to symbol file.') | 
|  | 464 | parser.add_argument( | 
|  | 465 | 'stub_src', type=os.path.realpath, | 
|  | 466 | help='Path to output stub source file.') | 
|  | 467 | parser.add_argument( | 
|  | 468 | 'version_script', type=os.path.realpath, | 
|  | 469 | help='Path to output version script.') | 
|  | 470 |  | 
|  | 471 | return parser.parse_args() | 
|  | 472 |  | 
|  | 473 |  | 
|  | 474 | def main(): | 
|  | 475 | """Program entry point.""" | 
|  | 476 | args = parse_args() | 
|  | 477 |  | 
| Dan Albert | 3f6fb2d | 2017-03-28 16:04:25 -0700 | [diff] [blame] | 478 | with open(args.api_map) as map_file: | 
|  | 479 | api_map = json.load(map_file) | 
|  | 480 | api = decode_api_level(args.api, api_map) | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 481 |  | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 482 | verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) | 
|  | 483 | verbosity = args.verbose | 
|  | 484 | if verbosity > 2: | 
|  | 485 | verbosity = 2 | 
|  | 486 | logging.basicConfig(level=verbose_map[verbosity]) | 
|  | 487 |  | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 488 | with open(args.symbol_file) as symbol_file: | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 489 | try: | 
|  | 490 | versions = SymbolFileParser(symbol_file, api_map, args.arch, api, | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 491 | args.vndk, args.apex).parse() | 
| Dan Albert | 756f2d0 | 2018-10-09 16:36:03 -0700 | [diff] [blame] | 492 | except MultiplyDefinedSymbolError as ex: | 
|  | 493 | sys.exit('{}: error: {}'.format(args.symbol_file, ex)) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 494 |  | 
|  | 495 | with open(args.stub_src, 'w') as src_file: | 
|  | 496 | with open(args.version_script, 'w') as version_file: | 
| Dan Albert | 49927d2 | 2017-03-28 15:00:46 -0700 | [diff] [blame] | 497 | generator = Generator(src_file, version_file, args.arch, api, | 
| Jiyong Park | bb4e135 | 2018-12-07 15:54:52 +0900 | [diff] [blame] | 498 | args.vndk, args.apex) | 
| Dan Albert | 8bdccb9 | 2016-07-29 13:06:22 -0700 | [diff] [blame] | 499 | generator.write(versions) | 
| Dan Albert | 914449f | 2016-06-17 16:45:24 -0700 | [diff] [blame] | 500 |  | 
|  | 501 |  | 
|  | 502 | if __name__ == '__main__': | 
|  | 503 | main() |