1*333d2b36SAndroid Build Coastguard Worker# 2*333d2b36SAndroid Build Coastguard Worker# Copyright (C) 2016 The Android Open Source Project 3*333d2b36SAndroid Build Coastguard Worker# 4*333d2b36SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*333d2b36SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*333d2b36SAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*333d2b36SAndroid Build Coastguard Worker# 8*333d2b36SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*333d2b36SAndroid Build Coastguard Worker# 10*333d2b36SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*333d2b36SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*333d2b36SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*333d2b36SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*333d2b36SAndroid Build Coastguard Worker# limitations under the License. 15*333d2b36SAndroid Build Coastguard Worker# 16*333d2b36SAndroid Build Coastguard Worker"""Parser for Android's version script information.""" 17*333d2b36SAndroid Build Coastguard Workerfrom __future__ import annotations 18*333d2b36SAndroid Build Coastguard Worker 19*333d2b36SAndroid Build Coastguard Workerfrom dataclasses import dataclass, field 20*333d2b36SAndroid Build Coastguard Workerimport logging 21*333d2b36SAndroid Build Coastguard Workerimport re 22*333d2b36SAndroid Build Coastguard Workerfrom typing import ( 23*333d2b36SAndroid Build Coastguard Worker Dict, 24*333d2b36SAndroid Build Coastguard Worker Iterable, 25*333d2b36SAndroid Build Coastguard Worker Iterator, 26*333d2b36SAndroid Build Coastguard Worker List, 27*333d2b36SAndroid Build Coastguard Worker Mapping, 28*333d2b36SAndroid Build Coastguard Worker NewType, 29*333d2b36SAndroid Build Coastguard Worker Optional, 30*333d2b36SAndroid Build Coastguard Worker TextIO, 31*333d2b36SAndroid Build Coastguard Worker Tuple, 32*333d2b36SAndroid Build Coastguard Worker Union, 33*333d2b36SAndroid Build Coastguard Worker) 34*333d2b36SAndroid Build Coastguard Worker 35*333d2b36SAndroid Build Coastguard Worker 36*333d2b36SAndroid Build Coastguard WorkerApiMap = Mapping[str, int] 37*333d2b36SAndroid Build Coastguard WorkerArch = NewType('Arch', str) 38*333d2b36SAndroid Build Coastguard WorkerTag = NewType('Tag', str) 39*333d2b36SAndroid Build Coastguard Worker 40*333d2b36SAndroid Build Coastguard Worker 41*333d2b36SAndroid Build Coastguard WorkerALL_ARCHITECTURES = ( 42*333d2b36SAndroid Build Coastguard Worker Arch('arm'), 43*333d2b36SAndroid Build Coastguard Worker Arch('arm64'), 44*333d2b36SAndroid Build Coastguard Worker Arch('riscv64'), 45*333d2b36SAndroid Build Coastguard Worker Arch('x86'), 46*333d2b36SAndroid Build Coastguard Worker Arch('x86_64'), 47*333d2b36SAndroid Build Coastguard Worker) 48*333d2b36SAndroid Build Coastguard Worker 49*333d2b36SAndroid Build Coastguard Worker# TODO: it would be nice to dedupe with 'has_*_tag' property methods 50*333d2b36SAndroid Build Coastguard WorkerSUPPORTED_TAGS = ALL_ARCHITECTURES + ( 51*333d2b36SAndroid Build Coastguard Worker Tag('apex'), 52*333d2b36SAndroid Build Coastguard Worker Tag('llndk'), 53*333d2b36SAndroid Build Coastguard Worker Tag('platform-only'), 54*333d2b36SAndroid Build Coastguard Worker Tag('systemapi'), 55*333d2b36SAndroid Build Coastguard Worker Tag('var'), 56*333d2b36SAndroid Build Coastguard Worker Tag('weak'), 57*333d2b36SAndroid Build Coastguard Worker) 58*333d2b36SAndroid Build Coastguard Worker 59*333d2b36SAndroid Build Coastguard Worker# Arbitrary magic number. We use the same one in api-level.h for this purpose. 60*333d2b36SAndroid Build Coastguard WorkerFUTURE_API_LEVEL = 10000 61*333d2b36SAndroid Build Coastguard Worker 62*333d2b36SAndroid Build Coastguard Worker 63*333d2b36SAndroid Build Coastguard Workerdef logger() -> logging.Logger: 64*333d2b36SAndroid Build Coastguard Worker """Return the main logger for this module.""" 65*333d2b36SAndroid Build Coastguard Worker return logging.getLogger(__name__) 66*333d2b36SAndroid Build Coastguard Worker 67*333d2b36SAndroid Build Coastguard Worker 68*333d2b36SAndroid Build Coastguard Worker@dataclass 69*333d2b36SAndroid Build Coastguard Workerclass Tags: 70*333d2b36SAndroid Build Coastguard Worker """Container class for the tags attached to a symbol or version.""" 71*333d2b36SAndroid Build Coastguard Worker 72*333d2b36SAndroid Build Coastguard Worker tags: tuple[Tag, ...] = field(default_factory=tuple) 73*333d2b36SAndroid Build Coastguard Worker 74*333d2b36SAndroid Build Coastguard Worker @classmethod 75*333d2b36SAndroid Build Coastguard Worker def from_strs(cls, strs: Iterable[str]) -> Tags: 76*333d2b36SAndroid Build Coastguard Worker """Constructs tags from a collection of strings. 77*333d2b36SAndroid Build Coastguard Worker 78*333d2b36SAndroid Build Coastguard Worker Does not decode API levels. 79*333d2b36SAndroid Build Coastguard Worker """ 80*333d2b36SAndroid Build Coastguard Worker return Tags(tuple(Tag(s) for s in strs)) 81*333d2b36SAndroid Build Coastguard Worker 82*333d2b36SAndroid Build Coastguard Worker def __contains__(self, tag: Union[Tag, str]) -> bool: 83*333d2b36SAndroid Build Coastguard Worker return tag in self.tags 84*333d2b36SAndroid Build Coastguard Worker 85*333d2b36SAndroid Build Coastguard Worker def __iter__(self) -> Iterator[Tag]: 86*333d2b36SAndroid Build Coastguard Worker yield from self.tags 87*333d2b36SAndroid Build Coastguard Worker 88*333d2b36SAndroid Build Coastguard Worker @property 89*333d2b36SAndroid Build Coastguard Worker def has_mode_tags(self) -> bool: 90*333d2b36SAndroid Build Coastguard Worker """Returns True if any mode tags (apex, llndk, etc) are set.""" 91*333d2b36SAndroid Build Coastguard Worker return self.has_apex_tags or self.has_llndk_tags or self.has_systemapi_tags 92*333d2b36SAndroid Build Coastguard Worker 93*333d2b36SAndroid Build Coastguard Worker @property 94*333d2b36SAndroid Build Coastguard Worker def has_apex_tags(self) -> bool: 95*333d2b36SAndroid Build Coastguard Worker """Returns True if any APEX tags are set.""" 96*333d2b36SAndroid Build Coastguard Worker return 'apex' in self.tags 97*333d2b36SAndroid Build Coastguard Worker 98*333d2b36SAndroid Build Coastguard Worker @property 99*333d2b36SAndroid Build Coastguard Worker def has_systemapi_tags(self) -> bool: 100*333d2b36SAndroid Build Coastguard Worker """Returns True if any APEX tags are set.""" 101*333d2b36SAndroid Build Coastguard Worker return 'systemapi' in self.tags 102*333d2b36SAndroid Build Coastguard Worker 103*333d2b36SAndroid Build Coastguard Worker @property 104*333d2b36SAndroid Build Coastguard Worker def has_llndk_tags(self) -> bool: 105*333d2b36SAndroid Build Coastguard Worker """Returns True if any LL-NDK tags are set.""" 106*333d2b36SAndroid Build Coastguard Worker return 'llndk' in self.tags 107*333d2b36SAndroid Build Coastguard Worker 108*333d2b36SAndroid Build Coastguard Worker @property 109*333d2b36SAndroid Build Coastguard Worker def has_platform_only_tags(self) -> bool: 110*333d2b36SAndroid Build Coastguard Worker """Returns True if any platform-only tags are set.""" 111*333d2b36SAndroid Build Coastguard Worker return 'platform-only' in self.tags 112*333d2b36SAndroid Build Coastguard Worker 113*333d2b36SAndroid Build Coastguard Worker 114*333d2b36SAndroid Build Coastguard Worker@dataclass 115*333d2b36SAndroid Build Coastguard Workerclass Symbol: 116*333d2b36SAndroid Build Coastguard Worker """A symbol definition from a symbol file.""" 117*333d2b36SAndroid Build Coastguard Worker 118*333d2b36SAndroid Build Coastguard Worker name: str 119*333d2b36SAndroid Build Coastguard Worker tags: Tags 120*333d2b36SAndroid Build Coastguard Worker 121*333d2b36SAndroid Build Coastguard Worker 122*333d2b36SAndroid Build Coastguard Worker@dataclass 123*333d2b36SAndroid Build Coastguard Workerclass Version: 124*333d2b36SAndroid Build Coastguard Worker """A version block of a symbol file.""" 125*333d2b36SAndroid Build Coastguard Worker 126*333d2b36SAndroid Build Coastguard Worker name: str 127*333d2b36SAndroid Build Coastguard Worker base: Optional[str] 128*333d2b36SAndroid Build Coastguard Worker tags: Tags 129*333d2b36SAndroid Build Coastguard Worker symbols: List[Symbol] 130*333d2b36SAndroid Build Coastguard Worker 131*333d2b36SAndroid Build Coastguard Worker @property 132*333d2b36SAndroid Build Coastguard Worker def is_private(self) -> bool: 133*333d2b36SAndroid Build Coastguard Worker """Returns True if this version block is private (platform only).""" 134*333d2b36SAndroid Build Coastguard Worker return self.name.endswith('_PRIVATE') or self.name.endswith('_PLATFORM') 135*333d2b36SAndroid Build Coastguard Worker 136*333d2b36SAndroid Build Coastguard Worker 137*333d2b36SAndroid Build Coastguard Workerdef get_tags(line: str, api_map: ApiMap) -> Tags: 138*333d2b36SAndroid Build Coastguard Worker """Returns a list of all tags on this line.""" 139*333d2b36SAndroid Build Coastguard Worker _, _, all_tags = line.strip().partition('#') 140*333d2b36SAndroid Build Coastguard Worker return Tags(tuple( 141*333d2b36SAndroid Build Coastguard Worker decode_api_level_tag(Tag(e), api_map) 142*333d2b36SAndroid Build Coastguard Worker for e in re.split(r'\s+', all_tags) if e.strip() 143*333d2b36SAndroid Build Coastguard Worker )) 144*333d2b36SAndroid Build Coastguard Worker 145*333d2b36SAndroid Build Coastguard Worker 146*333d2b36SAndroid Build Coastguard Workerdef is_api_level_tag(tag: Tag) -> bool: 147*333d2b36SAndroid Build Coastguard Worker """Returns true if this tag has an API level that may need decoding.""" 148*333d2b36SAndroid Build Coastguard Worker if tag.startswith('llndk-deprecated='): 149*333d2b36SAndroid Build Coastguard Worker return True 150*333d2b36SAndroid Build Coastguard Worker if tag.startswith('introduced='): 151*333d2b36SAndroid Build Coastguard Worker return True 152*333d2b36SAndroid Build Coastguard Worker if tag.startswith('introduced-'): 153*333d2b36SAndroid Build Coastguard Worker return True 154*333d2b36SAndroid Build Coastguard Worker if tag.startswith('versioned='): 155*333d2b36SAndroid Build Coastguard Worker return True 156*333d2b36SAndroid Build Coastguard Worker return False 157*333d2b36SAndroid Build Coastguard Worker 158*333d2b36SAndroid Build Coastguard Worker 159*333d2b36SAndroid Build Coastguard Workerdef decode_api_level(api: str, api_map: ApiMap) -> int: 160*333d2b36SAndroid Build Coastguard Worker """Decodes the API level argument into the API level number. 161*333d2b36SAndroid Build Coastguard Worker 162*333d2b36SAndroid Build Coastguard Worker For the average case, this just decodes the integer value from the string, 163*333d2b36SAndroid Build Coastguard Worker but for unreleased APIs we need to translate from the API codename (like 164*333d2b36SAndroid Build Coastguard Worker "O") to the future API level for that codename. 165*333d2b36SAndroid Build Coastguard Worker """ 166*333d2b36SAndroid Build Coastguard Worker try: 167*333d2b36SAndroid Build Coastguard Worker return int(api) 168*333d2b36SAndroid Build Coastguard Worker except ValueError: 169*333d2b36SAndroid Build Coastguard Worker pass 170*333d2b36SAndroid Build Coastguard Worker 171*333d2b36SAndroid Build Coastguard Worker if api == "current": 172*333d2b36SAndroid Build Coastguard Worker return FUTURE_API_LEVEL 173*333d2b36SAndroid Build Coastguard Worker 174*333d2b36SAndroid Build Coastguard Worker return api_map[api] 175*333d2b36SAndroid Build Coastguard Worker 176*333d2b36SAndroid Build Coastguard Worker 177*333d2b36SAndroid Build Coastguard Workerdef decode_api_level_tag(tag: Tag, api_map: ApiMap) -> Tag: 178*333d2b36SAndroid Build Coastguard Worker """Decodes API level code name in a tag. 179*333d2b36SAndroid Build Coastguard Worker 180*333d2b36SAndroid Build Coastguard Worker Raises: 181*333d2b36SAndroid Build Coastguard Worker ParseError: An unknown version name was found in a tag. 182*333d2b36SAndroid Build Coastguard Worker """ 183*333d2b36SAndroid Build Coastguard Worker if not is_api_level_tag(tag): 184*333d2b36SAndroid Build Coastguard Worker if tag not in SUPPORTED_TAGS: 185*333d2b36SAndroid Build Coastguard Worker raise ParseError(f'Unsupported tag: {tag}') 186*333d2b36SAndroid Build Coastguard Worker 187*333d2b36SAndroid Build Coastguard Worker return tag 188*333d2b36SAndroid Build Coastguard Worker 189*333d2b36SAndroid Build Coastguard Worker name, value = split_tag(tag) 190*333d2b36SAndroid Build Coastguard Worker try: 191*333d2b36SAndroid Build Coastguard Worker decoded = str(decode_api_level(value, api_map)) 192*333d2b36SAndroid Build Coastguard Worker return Tag(f'{name}={decoded}') 193*333d2b36SAndroid Build Coastguard Worker except KeyError as ex: 194*333d2b36SAndroid Build Coastguard Worker raise ParseError(f'Unknown version name in tag: {tag}') from ex 195*333d2b36SAndroid Build Coastguard Worker 196*333d2b36SAndroid Build Coastguard Worker 197*333d2b36SAndroid Build Coastguard Workerdef split_tag(tag: Tag) -> Tuple[str, str]: 198*333d2b36SAndroid Build Coastguard Worker """Returns a key/value tuple of the tag. 199*333d2b36SAndroid Build Coastguard Worker 200*333d2b36SAndroid Build Coastguard Worker Raises: 201*333d2b36SAndroid Build Coastguard Worker ValueError: Tag is not a key/value type tag. 202*333d2b36SAndroid Build Coastguard Worker 203*333d2b36SAndroid Build Coastguard Worker Returns: Tuple of (key, value) of the tag. Both components are strings. 204*333d2b36SAndroid Build Coastguard Worker """ 205*333d2b36SAndroid Build Coastguard Worker if '=' not in tag: 206*333d2b36SAndroid Build Coastguard Worker raise ValueError('Not a key/value tag: ' + tag) 207*333d2b36SAndroid Build Coastguard Worker key, _, value = tag.partition('=') 208*333d2b36SAndroid Build Coastguard Worker return key, value 209*333d2b36SAndroid Build Coastguard Worker 210*333d2b36SAndroid Build Coastguard Worker 211*333d2b36SAndroid Build Coastguard Workerdef get_tag_value(tag: Tag) -> str: 212*333d2b36SAndroid Build Coastguard Worker """Returns the value of a key/value tag. 213*333d2b36SAndroid Build Coastguard Worker 214*333d2b36SAndroid Build Coastguard Worker Raises: 215*333d2b36SAndroid Build Coastguard Worker ValueError: Tag is not a key/value type tag. 216*333d2b36SAndroid Build Coastguard Worker 217*333d2b36SAndroid Build Coastguard Worker Returns: Value part of tag as a string. 218*333d2b36SAndroid Build Coastguard Worker """ 219*333d2b36SAndroid Build Coastguard Worker return split_tag(tag)[1] 220*333d2b36SAndroid Build Coastguard Worker 221*333d2b36SAndroid Build Coastguard Workerclass Filter: 222*333d2b36SAndroid Build Coastguard Worker """A filter encapsulates a condition that tells whether a version or a 223*333d2b36SAndroid Build Coastguard Worker symbol should be omitted or not 224*333d2b36SAndroid Build Coastguard Worker """ 225*333d2b36SAndroid Build Coastguard Worker 226*333d2b36SAndroid Build Coastguard Worker def __init__(self, arch: Arch, api: int, llndk: bool = False, apex: bool = False, systemapi: 227*333d2b36SAndroid Build Coastguard Worker bool = False, ndk: bool = True): 228*333d2b36SAndroid Build Coastguard Worker self.arch = arch 229*333d2b36SAndroid Build Coastguard Worker self.api = api 230*333d2b36SAndroid Build Coastguard Worker self.llndk = llndk 231*333d2b36SAndroid Build Coastguard Worker self.apex = apex 232*333d2b36SAndroid Build Coastguard Worker self.systemapi = systemapi 233*333d2b36SAndroid Build Coastguard Worker self.ndk = ndk 234*333d2b36SAndroid Build Coastguard Worker 235*333d2b36SAndroid Build Coastguard Worker def _symbol_in_arch_api(self, tags: Tags) -> bool: 236*333d2b36SAndroid Build Coastguard Worker if not symbol_in_arch(tags, self.arch): 237*333d2b36SAndroid Build Coastguard Worker return True 238*333d2b36SAndroid Build Coastguard Worker if not symbol_in_api(tags, self.arch, self.api): 239*333d2b36SAndroid Build Coastguard Worker return True 240*333d2b36SAndroid Build Coastguard Worker return False 241*333d2b36SAndroid Build Coastguard Worker 242*333d2b36SAndroid Build Coastguard Worker def _should_omit_tags(self, tags: Tags) -> bool: 243*333d2b36SAndroid Build Coastguard Worker """Returns True if the tagged object should be omitted. 244*333d2b36SAndroid Build Coastguard Worker 245*333d2b36SAndroid Build Coastguard Worker This defines the rules shared between version tagging and symbol tagging. 246*333d2b36SAndroid Build Coastguard Worker """ 247*333d2b36SAndroid Build Coastguard Worker # The apex and llndk tags will only exclude APIs from other modes. If in 248*333d2b36SAndroid Build Coastguard Worker # APEX or LLNDK mode and neither tag is provided, we fall back to the 249*333d2b36SAndroid Build Coastguard Worker # default behavior because all NDK symbols are implicitly available to 250*333d2b36SAndroid Build Coastguard Worker # APEX and LLNDK. 251*333d2b36SAndroid Build Coastguard Worker if tags.has_mode_tags: 252*333d2b36SAndroid Build Coastguard Worker if self.apex and tags.has_apex_tags: 253*333d2b36SAndroid Build Coastguard Worker return False 254*333d2b36SAndroid Build Coastguard Worker if self.systemapi and tags.has_systemapi_tags: 255*333d2b36SAndroid Build Coastguard Worker return False 256*333d2b36SAndroid Build Coastguard Worker if self.llndk and tags.has_llndk_tags: 257*333d2b36SAndroid Build Coastguard Worker return self._symbol_in_arch_api(tags) 258*333d2b36SAndroid Build Coastguard Worker return True 259*333d2b36SAndroid Build Coastguard Worker return self._symbol_in_arch_api(tags) 260*333d2b36SAndroid Build Coastguard Worker 261*333d2b36SAndroid Build Coastguard Worker def should_omit_version(self, version: Version) -> bool: 262*333d2b36SAndroid Build Coastguard Worker """Returns True if the version section should be omitted. 263*333d2b36SAndroid Build Coastguard Worker 264*333d2b36SAndroid Build Coastguard Worker We want to omit any sections that do not have any symbols we'll have in 265*333d2b36SAndroid Build Coastguard Worker the stub library. Sections that contain entirely future symbols or only 266*333d2b36SAndroid Build Coastguard Worker symbols for certain architectures. 267*333d2b36SAndroid Build Coastguard Worker """ 268*333d2b36SAndroid Build Coastguard Worker if version.is_private: 269*333d2b36SAndroid Build Coastguard Worker return True 270*333d2b36SAndroid Build Coastguard Worker if version.tags.has_platform_only_tags: 271*333d2b36SAndroid Build Coastguard Worker return True 272*333d2b36SAndroid Build Coastguard Worker return self._should_omit_tags(version.tags) 273*333d2b36SAndroid Build Coastguard Worker 274*333d2b36SAndroid Build Coastguard Worker def should_omit_symbol(self, symbol: Symbol) -> bool: 275*333d2b36SAndroid Build Coastguard Worker """Returns True if the symbol should be omitted.""" 276*333d2b36SAndroid Build Coastguard Worker if not symbol.tags.has_mode_tags and not self.ndk: 277*333d2b36SAndroid Build Coastguard Worker # Symbols that don't have mode tags are NDK. They are usually 278*333d2b36SAndroid Build Coastguard Worker # included, but have to be omitted if NDK symbols are explicitly 279*333d2b36SAndroid Build Coastguard Worker # filtered-out 280*333d2b36SAndroid Build Coastguard Worker return True 281*333d2b36SAndroid Build Coastguard Worker 282*333d2b36SAndroid Build Coastguard Worker return self._should_omit_tags(symbol.tags) 283*333d2b36SAndroid Build Coastguard Worker 284*333d2b36SAndroid Build Coastguard Worker 285*333d2b36SAndroid Build Coastguard Workerdef symbol_in_arch(tags: Tags, arch: Arch) -> bool: 286*333d2b36SAndroid Build Coastguard Worker """Returns true if the symbol is present for the given architecture.""" 287*333d2b36SAndroid Build Coastguard Worker has_arch_tags = False 288*333d2b36SAndroid Build Coastguard Worker for tag in tags: 289*333d2b36SAndroid Build Coastguard Worker if tag == arch: 290*333d2b36SAndroid Build Coastguard Worker return True 291*333d2b36SAndroid Build Coastguard Worker if tag in ALL_ARCHITECTURES: 292*333d2b36SAndroid Build Coastguard Worker has_arch_tags = True 293*333d2b36SAndroid Build Coastguard Worker 294*333d2b36SAndroid Build Coastguard Worker # If there were no arch tags, the symbol is available for all 295*333d2b36SAndroid Build Coastguard Worker # architectures. If there were any arch tags, the symbol is only available 296*333d2b36SAndroid Build Coastguard Worker # for the tagged architectures. 297*333d2b36SAndroid Build Coastguard Worker return not has_arch_tags 298*333d2b36SAndroid Build Coastguard Worker 299*333d2b36SAndroid Build Coastguard Worker 300*333d2b36SAndroid Build Coastguard Workerdef symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool: 301*333d2b36SAndroid Build Coastguard Worker """Returns true if the symbol is present for the given API level.""" 302*333d2b36SAndroid Build Coastguard Worker introduced_tag = None 303*333d2b36SAndroid Build Coastguard Worker arch_specific = False 304*333d2b36SAndroid Build Coastguard Worker for tag in tags: 305*333d2b36SAndroid Build Coastguard Worker # If there is an arch-specific tag, it should override the common one. 306*333d2b36SAndroid Build Coastguard Worker if tag.startswith('introduced=') and not arch_specific: 307*333d2b36SAndroid Build Coastguard Worker introduced_tag = tag 308*333d2b36SAndroid Build Coastguard Worker elif tag.startswith('introduced-' + arch + '='): 309*333d2b36SAndroid Build Coastguard Worker introduced_tag = tag 310*333d2b36SAndroid Build Coastguard Worker arch_specific = True 311*333d2b36SAndroid Build Coastguard Worker elif tag == 'future': 312*333d2b36SAndroid Build Coastguard Worker return api == FUTURE_API_LEVEL 313*333d2b36SAndroid Build Coastguard Worker 314*333d2b36SAndroid Build Coastguard Worker if introduced_tag is None: 315*333d2b36SAndroid Build Coastguard Worker # We found no "introduced" tags, so the symbol has always been 316*333d2b36SAndroid Build Coastguard Worker # available. 317*333d2b36SAndroid Build Coastguard Worker return True 318*333d2b36SAndroid Build Coastguard Worker 319*333d2b36SAndroid Build Coastguard Worker return api >= int(get_tag_value(introduced_tag)) 320*333d2b36SAndroid Build Coastguard Worker 321*333d2b36SAndroid Build Coastguard Worker 322*333d2b36SAndroid Build Coastguard Workerdef symbol_versioned_in_api(tags: Iterable[Tag], api: int) -> bool: 323*333d2b36SAndroid Build Coastguard Worker """Returns true if the symbol should be versioned for the given API. 324*333d2b36SAndroid Build Coastguard Worker 325*333d2b36SAndroid Build Coastguard Worker This models the `versioned=API` tag. This should be a very uncommonly 326*333d2b36SAndroid Build Coastguard Worker needed tag, and is really only needed to fix versioning mistakes that are 327*333d2b36SAndroid Build Coastguard Worker already out in the wild. 328*333d2b36SAndroid Build Coastguard Worker 329*333d2b36SAndroid Build Coastguard Worker For example, some of libc's __aeabi_* functions were originally placed in 330*333d2b36SAndroid Build Coastguard Worker the private version, but that was incorrect. They are now in LIBC_N, but 331*333d2b36SAndroid Build Coastguard Worker when building against any version prior to N we need the symbol to be 332*333d2b36SAndroid Build Coastguard Worker unversioned (otherwise it won't resolve on M where it is private). 333*333d2b36SAndroid Build Coastguard Worker """ 334*333d2b36SAndroid Build Coastguard Worker for tag in tags: 335*333d2b36SAndroid Build Coastguard Worker if tag.startswith('versioned='): 336*333d2b36SAndroid Build Coastguard Worker return api >= int(get_tag_value(tag)) 337*333d2b36SAndroid Build Coastguard Worker # If there is no "versioned" tag, the tag has been versioned for as long as 338*333d2b36SAndroid Build Coastguard Worker # it was introduced. 339*333d2b36SAndroid Build Coastguard Worker return True 340*333d2b36SAndroid Build Coastguard Worker 341*333d2b36SAndroid Build Coastguard Worker 342*333d2b36SAndroid Build Coastguard Workerclass ParseError(RuntimeError): 343*333d2b36SAndroid Build Coastguard Worker """An error that occurred while parsing a symbol file.""" 344*333d2b36SAndroid Build Coastguard Worker 345*333d2b36SAndroid Build Coastguard Worker 346*333d2b36SAndroid Build Coastguard Workerclass MultiplyDefinedSymbolError(RuntimeError): 347*333d2b36SAndroid Build Coastguard Worker """A symbol name was multiply defined.""" 348*333d2b36SAndroid Build Coastguard Worker def __init__(self, multiply_defined_symbols: Iterable[str]) -> None: 349*333d2b36SAndroid Build Coastguard Worker super().__init__( 350*333d2b36SAndroid Build Coastguard Worker 'Version script contains multiple definitions for: {}'.format( 351*333d2b36SAndroid Build Coastguard Worker ', '.join(multiply_defined_symbols))) 352*333d2b36SAndroid Build Coastguard Worker self.multiply_defined_symbols = multiply_defined_symbols 353*333d2b36SAndroid Build Coastguard Worker 354*333d2b36SAndroid Build Coastguard Worker 355*333d2b36SAndroid Build Coastguard Workerclass SymbolFileParser: 356*333d2b36SAndroid Build Coastguard Worker """Parses NDK symbol files.""" 357*333d2b36SAndroid Build Coastguard Worker def __init__(self, input_file: TextIO, api_map: ApiMap, filt: Filter) -> None: 358*333d2b36SAndroid Build Coastguard Worker self.input_file = input_file 359*333d2b36SAndroid Build Coastguard Worker self.api_map = api_map 360*333d2b36SAndroid Build Coastguard Worker self.filter = filt 361*333d2b36SAndroid Build Coastguard Worker self.current_line: Optional[str] = None 362*333d2b36SAndroid Build Coastguard Worker 363*333d2b36SAndroid Build Coastguard Worker def parse(self) -> List[Version]: 364*333d2b36SAndroid Build Coastguard Worker """Parses the symbol file and returns a list of Version objects.""" 365*333d2b36SAndroid Build Coastguard Worker versions = [] 366*333d2b36SAndroid Build Coastguard Worker while self.next_line(): 367*333d2b36SAndroid Build Coastguard Worker assert self.current_line is not None 368*333d2b36SAndroid Build Coastguard Worker if '{' in self.current_line: 369*333d2b36SAndroid Build Coastguard Worker versions.append(self.parse_version()) 370*333d2b36SAndroid Build Coastguard Worker else: 371*333d2b36SAndroid Build Coastguard Worker raise ParseError( 372*333d2b36SAndroid Build Coastguard Worker f'Unexpected contents at top level: {self.current_line}') 373*333d2b36SAndroid Build Coastguard Worker 374*333d2b36SAndroid Build Coastguard Worker self.check_no_duplicate_symbols(versions) 375*333d2b36SAndroid Build Coastguard Worker return versions 376*333d2b36SAndroid Build Coastguard Worker 377*333d2b36SAndroid Build Coastguard Worker def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None: 378*333d2b36SAndroid Build Coastguard Worker """Raises errors for multiply defined symbols. 379*333d2b36SAndroid Build Coastguard Worker 380*333d2b36SAndroid Build Coastguard Worker This situation is the normal case when symbol versioning is actually 381*333d2b36SAndroid Build Coastguard Worker used, but this script doesn't currently handle that. The error message 382*333d2b36SAndroid Build Coastguard Worker will be a not necessarily obvious "error: redefition of 'foo'" from 383*333d2b36SAndroid Build Coastguard Worker stub.c, so it's better for us to catch this situation and raise a 384*333d2b36SAndroid Build Coastguard Worker better error. 385*333d2b36SAndroid Build Coastguard Worker """ 386*333d2b36SAndroid Build Coastguard Worker symbol_names = set() 387*333d2b36SAndroid Build Coastguard Worker multiply_defined_symbols = set() 388*333d2b36SAndroid Build Coastguard Worker for version in versions: 389*333d2b36SAndroid Build Coastguard Worker if self.filter.should_omit_version(version): 390*333d2b36SAndroid Build Coastguard Worker continue 391*333d2b36SAndroid Build Coastguard Worker 392*333d2b36SAndroid Build Coastguard Worker for symbol in version.symbols: 393*333d2b36SAndroid Build Coastguard Worker if self.filter.should_omit_symbol(symbol): 394*333d2b36SAndroid Build Coastguard Worker continue 395*333d2b36SAndroid Build Coastguard Worker 396*333d2b36SAndroid Build Coastguard Worker if symbol.name in symbol_names: 397*333d2b36SAndroid Build Coastguard Worker multiply_defined_symbols.add(symbol.name) 398*333d2b36SAndroid Build Coastguard Worker symbol_names.add(symbol.name) 399*333d2b36SAndroid Build Coastguard Worker if multiply_defined_symbols: 400*333d2b36SAndroid Build Coastguard Worker raise MultiplyDefinedSymbolError( 401*333d2b36SAndroid Build Coastguard Worker sorted(list(multiply_defined_symbols))) 402*333d2b36SAndroid Build Coastguard Worker 403*333d2b36SAndroid Build Coastguard Worker def parse_version(self) -> Version: 404*333d2b36SAndroid Build Coastguard Worker """Parses a single version section and returns a Version object.""" 405*333d2b36SAndroid Build Coastguard Worker assert self.current_line is not None 406*333d2b36SAndroid Build Coastguard Worker name = self.current_line.split('{')[0].strip() 407*333d2b36SAndroid Build Coastguard Worker tags = get_tags(self.current_line, self.api_map) 408*333d2b36SAndroid Build Coastguard Worker symbols: List[Symbol] = [] 409*333d2b36SAndroid Build Coastguard Worker global_scope = True 410*333d2b36SAndroid Build Coastguard Worker cpp_symbols = False 411*333d2b36SAndroid Build Coastguard Worker while self.next_line(): 412*333d2b36SAndroid Build Coastguard Worker if '}' in self.current_line: 413*333d2b36SAndroid Build Coastguard Worker # Line is something like '} BASE; # tags'. Both base and tags 414*333d2b36SAndroid Build Coastguard Worker # are optional here. 415*333d2b36SAndroid Build Coastguard Worker base = self.current_line.partition('}')[2] 416*333d2b36SAndroid Build Coastguard Worker base = base.partition('#')[0].strip() 417*333d2b36SAndroid Build Coastguard Worker if not base.endswith(';'): 418*333d2b36SAndroid Build Coastguard Worker raise ParseError( 419*333d2b36SAndroid Build Coastguard Worker 'Unterminated version/export "C++" block (expected ;).') 420*333d2b36SAndroid Build Coastguard Worker if cpp_symbols: 421*333d2b36SAndroid Build Coastguard Worker cpp_symbols = False 422*333d2b36SAndroid Build Coastguard Worker else: 423*333d2b36SAndroid Build Coastguard Worker base = base.rstrip(';').rstrip() 424*333d2b36SAndroid Build Coastguard Worker return Version(name, base or None, tags, symbols) 425*333d2b36SAndroid Build Coastguard Worker elif 'extern "C++" {' in self.current_line: 426*333d2b36SAndroid Build Coastguard Worker cpp_symbols = True 427*333d2b36SAndroid Build Coastguard Worker elif not cpp_symbols and ':' in self.current_line: 428*333d2b36SAndroid Build Coastguard Worker visibility = self.current_line.split(':')[0].strip() 429*333d2b36SAndroid Build Coastguard Worker if visibility == 'local': 430*333d2b36SAndroid Build Coastguard Worker global_scope = False 431*333d2b36SAndroid Build Coastguard Worker elif visibility == 'global': 432*333d2b36SAndroid Build Coastguard Worker global_scope = True 433*333d2b36SAndroid Build Coastguard Worker else: 434*333d2b36SAndroid Build Coastguard Worker raise ParseError('Unknown visiblity label: ' + visibility) 435*333d2b36SAndroid Build Coastguard Worker elif global_scope and not cpp_symbols: 436*333d2b36SAndroid Build Coastguard Worker symbols.append(self.parse_symbol()) 437*333d2b36SAndroid Build Coastguard Worker else: 438*333d2b36SAndroid Build Coastguard Worker # We're in a hidden scope or in 'extern "C++"' block. Ignore 439*333d2b36SAndroid Build Coastguard Worker # everything. 440*333d2b36SAndroid Build Coastguard Worker pass 441*333d2b36SAndroid Build Coastguard Worker raise ParseError('Unexpected EOF in version block.') 442*333d2b36SAndroid Build Coastguard Worker 443*333d2b36SAndroid Build Coastguard Worker def parse_symbol(self) -> Symbol: 444*333d2b36SAndroid Build Coastguard Worker """Parses a single symbol line and returns a Symbol object.""" 445*333d2b36SAndroid Build Coastguard Worker assert self.current_line is not None 446*333d2b36SAndroid Build Coastguard Worker if ';' not in self.current_line: 447*333d2b36SAndroid Build Coastguard Worker raise ParseError( 448*333d2b36SAndroid Build Coastguard Worker 'Expected ; to terminate symbol: ' + self.current_line) 449*333d2b36SAndroid Build Coastguard Worker if '*' in self.current_line: 450*333d2b36SAndroid Build Coastguard Worker raise ParseError( 451*333d2b36SAndroid Build Coastguard Worker 'Wildcard global symbols are not permitted.') 452*333d2b36SAndroid Build Coastguard Worker # Line is now in the format "<symbol-name>; # tags" 453*333d2b36SAndroid Build Coastguard Worker name, _, _ = self.current_line.strip().partition(';') 454*333d2b36SAndroid Build Coastguard Worker tags = get_tags(self.current_line, self.api_map) 455*333d2b36SAndroid Build Coastguard Worker return Symbol(name, tags) 456*333d2b36SAndroid Build Coastguard Worker 457*333d2b36SAndroid Build Coastguard Worker def next_line(self) -> str: 458*333d2b36SAndroid Build Coastguard Worker """Returns the next non-empty non-comment line. 459*333d2b36SAndroid Build Coastguard Worker 460*333d2b36SAndroid Build Coastguard Worker A return value of '' indicates EOF. 461*333d2b36SAndroid Build Coastguard Worker """ 462*333d2b36SAndroid Build Coastguard Worker line = self.input_file.readline() 463*333d2b36SAndroid Build Coastguard Worker while not line.strip() or line.strip().startswith('#'): 464*333d2b36SAndroid Build Coastguard Worker line = self.input_file.readline() 465*333d2b36SAndroid Build Coastguard Worker 466*333d2b36SAndroid Build Coastguard Worker # We want to skip empty lines, but '' indicates EOF. 467*333d2b36SAndroid Build Coastguard Worker if not line: 468*333d2b36SAndroid Build Coastguard Worker break 469*333d2b36SAndroid Build Coastguard Worker self.current_line = line 470*333d2b36SAndroid Build Coastguard Worker return self.current_line 471