Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3420b834 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes I0aee679a,I7fb380a3

* changes:
  Add systemapi as an APEX synonym for stub maps.
  Refactor tag handling code in stub generator.
parents 45d7289e 56f52de8
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -108,7 +108,14 @@ def parse_args() -> argparse.Namespace:
    parser.add_argument(
        '--llndk', action='store_true', help='Use the LLNDK variant.')
    parser.add_argument(
        '--apex', action='store_true', help='Use the APEX variant.')
        '--apex',
        action='store_true',
        help='Use the APEX variant. Note: equivalent to --system-api.')
    parser.add_argument(
        '--system-api',
        action='store_true',
        dest='apex',
        help='Use the SystemAPI variant. Note: equivalent to --apex.')

    parser.add_argument('--api-map',
                        type=resolved_path,
+30 −28
Original line number Diff line number Diff line
@@ -19,9 +19,10 @@ import io
import textwrap
import unittest

import ndkstubgen
import symbolfile
from symbolfile import Arch, Tag
from symbolfile import Arch, Tags

import ndkstubgen


# pylint: disable=missing-docstring
@@ -38,22 +39,24 @@ class GeneratorTest(unittest.TestCase):
                                         version_file, symbol_list_file,
                                         Arch('arm'), 9, False, False)

        version = symbolfile.Version('VERSION_PRIVATE', None, [], [
            symbolfile.Symbol('foo', []),
        version = symbolfile.Version('VERSION_PRIVATE', None, Tags(), [
            symbolfile.Symbol('foo', Tags()),
        ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
        self.assertEqual('', version_file.getvalue())

        version = symbolfile.Version('VERSION', None, [Tag('x86')], [
            symbolfile.Symbol('foo', []),
        version = symbolfile.Version('VERSION', None, Tags.from_strs(['x86']),
                                     [
                                         symbolfile.Symbol('foo', Tags()),
                                     ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
        self.assertEqual('', version_file.getvalue())

        version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [
            symbolfile.Symbol('foo', []),
        version = symbolfile.Version('VERSION', None,
                                     Tags.from_strs(['introduced=14']), [
                                         symbolfile.Symbol('foo', Tags()),
                                     ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
@@ -69,29 +72,29 @@ class GeneratorTest(unittest.TestCase):
                                         version_file, symbol_list_file,
                                         Arch('arm'), 9, False, False)

        version = symbolfile.Version('VERSION_1', None, [], [
            symbolfile.Symbol('foo', [Tag('x86')]),
        version = symbolfile.Version('VERSION_1', None, Tags(), [
            symbolfile.Symbol('foo', Tags.from_strs(['x86'])),
        ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
        self.assertEqual('', version_file.getvalue())

        version = symbolfile.Version('VERSION_1', None, [], [
            symbolfile.Symbol('foo', [Tag('introduced=14')]),
        version = symbolfile.Version('VERSION_1', None, Tags(), [
            symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])),
        ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
        self.assertEqual('', version_file.getvalue())

        version = symbolfile.Version('VERSION_1', None, [], [
            symbolfile.Symbol('foo', [Tag('llndk')]),
        version = symbolfile.Version('VERSION_1', None, Tags(), [
            symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
        ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
        self.assertEqual('', version_file.getvalue())

        version = symbolfile.Version('VERSION_1', None, [], [
            symbolfile.Symbol('foo', [Tag('apex')]),
        version = symbolfile.Version('VERSION_1', None, Tags(), [
            symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
        ])
        generator.write_version(version)
        self.assertEqual('', src_file.getvalue())
@@ -106,18 +109,17 @@ class GeneratorTest(unittest.TestCase):
                                         Arch('arm'), 9, False, False)

        versions = [
            symbolfile.Version('VERSION_1', None, [], [
                symbolfile.Symbol('foo', []),
                symbolfile.Symbol('bar', [Tag('var')]),
                symbolfile.Symbol('woodly', [Tag('weak')]),
                symbolfile.Symbol('doodly',
                                  [Tag('weak'), Tag('var')]),
            symbolfile.Version('VERSION_1', None, Tags(), [
                symbolfile.Symbol('foo', Tags()),
                symbolfile.Symbol('bar', Tags.from_strs(['var'])),
                symbolfile.Symbol('woodly', Tags.from_strs(['weak'])),
                symbolfile.Symbol('doodly', Tags.from_strs(['weak', 'var'])),
            ]),
            symbolfile.Version('VERSION_2', 'VERSION_1', [], [
                symbolfile.Symbol('baz', []),
            symbolfile.Version('VERSION_2', 'VERSION_1', Tags(), [
                symbolfile.Symbol('baz', Tags()),
            ]),
            symbolfile.Version('VERSION_3', 'VERSION_1', [], [
                symbolfile.Symbol('qux', [Tag('versioned=14')]),
            symbolfile.Version('VERSION_3', 'VERSION_1', Tags(), [
                symbolfile.Symbol('qux', Tags.from_strs(['versioned=14'])),
            ]),
        ]

+98 −53
Original line number Diff line number Diff line
@@ -14,18 +14,22 @@
# limitations under the License.
#
"""Parser for Android's version script information."""
from dataclasses import dataclass
from __future__ import annotations

from dataclasses import dataclass, field
import logging
import re
from typing import (
    Dict,
    Iterable,
    Iterator,
    List,
    Mapping,
    NewType,
    Optional,
    TextIO,
    Tuple,
    Union,
)


@@ -51,12 +55,53 @@ def logger() -> logging.Logger:
    return logging.getLogger(__name__)


@dataclass
class Tags:
    """Container class for the tags attached to a symbol or version."""

    tags: tuple[Tag, ...] = field(default_factory=tuple)

    @classmethod
    def from_strs(cls, strs: Iterable[str]) -> Tags:
        """Constructs tags from a collection of strings.

        Does not decode API levels.
        """
        return Tags(tuple(Tag(s) for s in strs))

    def __contains__(self, tag: Union[Tag, str]) -> bool:
        return tag in self.tags

    def __iter__(self) -> Iterator[Tag]:
        yield from self.tags

    @property
    def has_mode_tags(self) -> bool:
        """Returns True if any mode tags (apex, llndk, etc) are set."""
        return self.has_apex_tags or self.has_llndk_tags

    @property
    def has_apex_tags(self) -> bool:
        """Returns True if any APEX tags are set."""
        return 'apex' in self.tags or 'systemapi' in self.tags

    @property
    def has_llndk_tags(self) -> bool:
        """Returns True if any LL-NDK tags are set."""
        return 'llndk' in self.tags

    @property
    def has_platform_only_tags(self) -> bool:
        """Returns True if any platform-only tags are set."""
        return 'platform-only' in self.tags


@dataclass
class Symbol:
    """A symbol definition from a symbol file."""

    name: str
    tags: List[Tag]
    tags: Tags


@dataclass
@@ -65,14 +110,22 @@ class Version:

    name: str
    base: Optional[str]
    tags: List[Tag]
    tags: Tags
    symbols: List[Symbol]

    @property
    def is_private(self) -> bool:
        """Returns True if this version block is private (platform only)."""
        return self.name.endswith('_PRIVATE') or self.name.endswith('_PLATFORM')

def get_tags(line: str) -> List[Tag]:

def get_tags(line: str, api_map: ApiMap) -> Tags:
    """Returns a list of all tags on this line."""
    _, _, all_tags = line.strip().partition('#')
    return [Tag(e) for e in re.split(r'\s+', all_tags) if e.strip()]
    return Tags(tuple(
        decode_api_level_tag(Tag(e), api_map)
        for e in re.split(r'\s+', all_tags) if e.strip()
    ))


def is_api_level_tag(tag: Tag) -> bool:
@@ -104,24 +157,21 @@ def decode_api_level(api: str, api_map: ApiMap) -> int:
    return api_map[api]


def decode_api_level_tags(tags: Iterable[Tag], api_map: ApiMap) -> List[Tag]:
    """Decodes API level code names in a list of tags.
def decode_api_level_tag(tag: Tag, api_map: ApiMap) -> Tag:
    """Decodes API level code name in a tag.

    Raises:
        ParseError: An unknown version name was found in a tag.
    """
    decoded_tags = list(tags)
    for idx, tag in enumerate(tags):
    if not is_api_level_tag(tag):
            continue
        name, value = split_tag(tag)
        return tag

    name, value = split_tag(tag)
    try:
        decoded = str(decode_api_level(value, api_map))
            decoded_tags[idx] = Tag('='.join([name, decoded]))
        except KeyError:
            raise ParseError(f'Unknown version name in tag: {tag}')
    return decoded_tags
        return Tag(f'{name}={decoded}')
    except KeyError as ex:
        raise ParseError(f'Unknown version name in tag: {tag}') from ex


def split_tag(tag: Tag) -> Tuple[str, str]:
@@ -149,55 +199,52 @@ def get_tag_value(tag: Tag) -> str:
    return split_tag(tag)[1]


def version_is_private(version: str) -> bool:
    """Returns True if the version name should be treated as private."""
    return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
def _should_omit_tags(tags: Tags, arch: Arch, api: int, llndk: bool,
                      apex: bool) -> bool:
    """Returns True if the tagged object should be omitted.

    This defines the rules shared between version tagging and symbol tagging.
    """
    # The apex and llndk tags will only exclude APIs from other modes. If in
    # APEX or LLNDK mode and neither tag is provided, we fall back to the
    # default behavior because all NDK symbols are implicitly available to APEX
    # and LLNDK.
    if tags.has_mode_tags:
        if not apex and not llndk:
            return True
        if apex and not tags.has_apex_tags:
            return True
        if llndk and not tags.has_llndk_tags:
            return True
    if not symbol_in_arch(tags, arch):
        return True
    if not symbol_in_api(tags, arch, api):
        return True
    return False


def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool,
                        apex: bool) -> bool:
    """Returns True if the version section should be ommitted.
    """Returns True if the version section should be omitted.

    We want to omit any sections that do not have any symbols we'll have in the
    stub library. Sections that contain entirely future symbols or only symbols
    for certain architectures.
    """
    if version_is_private(version.name):
    if version.is_private:
        return True
    if 'platform-only' in version.tags:
    if version.tags.has_platform_only_tags:
        return True

    no_llndk_no_apex = ('llndk' not in version.tags
                        and 'apex' not in version.tags)
    keep = no_llndk_no_apex or \
           ('llndk' in version.tags and llndk) or \
           ('apex' in version.tags and apex)
    if not keep:
        return True
    if not symbol_in_arch(version.tags, arch):
        return True
    if not symbol_in_api(version.tags, arch, api):
        return True
    return False
    return _should_omit_tags(version.tags, arch, api, llndk, apex)


def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool,
                       apex: bool) -> bool:
    """Returns True if the symbol should be omitted."""
    no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags
    keep = no_llndk_no_apex or \
           ('llndk' in symbol.tags and llndk) or \
           ('apex' in symbol.tags and apex)
    if not keep:
        return True
    if not symbol_in_arch(symbol.tags, arch):
        return True
    if not symbol_in_api(symbol.tags, arch, api):
        return True
    return False
    return _should_omit_tags(symbol.tags, arch, api, llndk, apex)


def symbol_in_arch(tags: Iterable[Tag], arch: Arch) -> bool:
def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
    """Returns true if the symbol is present for the given architecture."""
    has_arch_tags = False
    for tag in tags:
@@ -325,8 +372,7 @@ class SymbolFileParser:
        """Parses a single version section and returns a Version object."""
        assert self.current_line is not None
        name = self.current_line.split('{')[0].strip()
        tags = get_tags(self.current_line)
        tags = decode_api_level_tags(tags, self.api_map)
        tags = get_tags(self.current_line, self.api_map)
        symbols: List[Symbol] = []
        global_scope = True
        cpp_symbols = False
@@ -373,8 +419,7 @@ class SymbolFileParser:
                'Wildcard global symbols are not permitted.')
        # Line is now in the format "<symbol-name>; # tags"
        name, _, _ = self.current_line.strip().partition(';')
        tags = get_tags(self.current_line)
        tags = decode_api_level_tags(tags, self.api_map)
        tags = get_tags(self.current_line, self.api_map)
        return Symbol(name, tags)

    def next_line(self) -> str:
+129 −88

File changed.

Preview size limit exceeded, changes collapsed.

+11 −3
Original line number Diff line number Diff line
@@ -70,9 +70,11 @@ the same line. The supported tags are:

### apex

Indicates that the version or symbol is to be exposed in the APEX stubs rather
than the NDK. May be used in combination with `llndk` if the symbol is exposed
to both APEX and the LL-NDK.
Indicates that the version or symbol is to be exposed by an APEX rather than the
NDK. For APIs exposed by the platform *for* APEX, use `systemapi`.

May be used in combination with `llndk` if the symbol is exposed to both APEX
and the LL-NDK.

### future

@@ -144,6 +146,12 @@ for use by the NDK, LL-NDK, or APEX (similar to Java's `@SystemAPI`). It is
preferable to keep such APIs in an entirely separate library to protect them
from access via `dlsym`, but this is not always possible.

### systemapi

This is a synonym of the `apex` tag. It should be used to clarify that the API
is an API exposed by the system for an APEX, whereas `apex` should be used for
APIs exposed by an APEX to the platform or another APEX.

### var

Used to define a public global variable. By default all symbols are exposed as