xref: /aosp_15_r20/build/soong/cc/symbolfile/__init__.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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