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